首頁 > 後端開發 > C#.Net教程 > 泛型的概述與具體使用

泛型的概述與具體使用

零下一度
發布: 2017-06-23 16:32:42
原創
1576 人瀏覽過

一、泛型概述

      泛型類別與泛型方法兼重複使用性類型安全性高效率於一身,是與之對應的非泛型的類別和方法所不及。泛型廣泛用於容器(collections)和對容器操作的方法。 .NET Framework 2.0的類別庫提供一個新的命名空間System.Collections.Generic,其中包含了一些新的基於泛型的容器類別。

  1. 泛型的可變型別參數:通常用T,但也可以用任意非關鍵字和保留字;

  2. #所有的可變類型T在編譯時,都採用佔位符的形式,在運行時將由實際傳入的類型來替換的所有的點位符;

 

二、泛型的優點

針對早期版本的通用語言運行時和C#語言的局限,泛型提供了一個解決方案。先前類型的泛化(generalization)是靠類型與全域基底類別System.Object的相互轉換來實現。 .NET Framework 基礎類別庫的ArrayList容器類,就是這種限制的一個例子。 ArrayList是一個很方便的容器類,使用中無需更改就可以儲存任何引用類型或值類型。

ArrayList list = new ArrayList();
list.Add(1);
list.Add(175.50);​​
list.Add("hello kitty");

double sum = 0;
foreach(int value in list)
{
     sum += value;
}

缺點:

      便利性是有代價的,這需要將任何一個加入ArrayList的引用型別或值類型都隱式地向上轉換成System.Object。如果這些元素是值類型,那麼當加入到清單中時,它們必須被裝箱;當重新取回它們時,要拆箱。型別轉換和裝箱、拆箱的操作都降低了效能;在必須迭代(iterate)大容器的情況下,裝箱和拆箱的影響可能十分顯著。另一個限制是缺乏編譯時的型別檢查,當一個ArrayList把任何型別轉換成Object,就無法在編譯時預防客戶碼中類似sum+=vlaue這樣的錯誤;

在System .Collections.Generic命名空間中的泛型List容器裡,同樣是把元素加入容器的操作,類似這樣:

與ArrayList相比,在客戶程式碼中唯一增加的List語法是宣告和實例化中的類型參數。程式碼略微複雜的回報是,你創建的表不僅比ArrayList更安全,而且明顯地更加快速,尤其當表中的元素是值類型的時候。

List listInt = new List();
     listInt.Add(100);
     listInt.Add(200);
  # #     listInt.Add("heel");  //編譯錯誤

    double sum = 0;

     foreach (int value in list)
  }



三、泛型的型別參數

      泛型型別或泛型方法的定義中,型別參數為佔位符(placeholder),通常為一個大寫字母(也可以使用任意非關鍵字和保留字的名字),如T。在客戶程式碼宣告、實例化該類型的變數時,把T替換為客戶代碼所指定的資料類型。泛型類,如泛型中給出的List類,不能用作as-is,原因在於它不是一個真正的類型,而更像是一個類型的藍圖。要使用MyList,客戶代碼必須在尖括號內指定一個類型參數,來宣告並實例化一個已建構類型(constructed type)。這個特定類別的類型參數可以是編譯器識別的任何類型。可以建立任意數量的已建構型別實例,每個使用不同的型別參數,如下:

List listInt = new List< int>();List listFloat = new List();

 

四、泛型類型參數的限制

#泛型提供了下列五種限制:

List listString = new List();




約束 描述
where T : struct 參數型別必須為值型別
where T : class 參數類型必須為引用類型
#where T : new() 參數類型必須有一個公有的無參構造函數。當與其它約束聯合使用時,new()約束必須放在最後。
where T : 參數類型必須為指定的基底類型或衍生自指定基底類型的子類別
#where T : 參數類型必須為指定的介面或指定介面的實作。可指定多個介面的約束。介面約束也可以是泛型的。

無限制型別參數:

  1. #不能使用!=和==對可變類型的實例進行比較,因為無法保證特定的型別參數支援這些運算子;

  2. #它們可以與System.Object相互轉換,也可明確地轉換成任何介面類型;

  3. 可以與null比較。如果一個無限制型別參數與null比較,當此型別參數為值型別時,比較的結果總為false。

無型別約束:當限制是泛型型別參數時,它就叫無型別約束(Naked type constraints)。

class List
{
     void Add(List items) where U : To
 
     }
}

#

在上面的範例中, Add方法的上下文中的T,就是一個無型別約束;而List類別的上下文中的T,則是一個無限制型別參數。

無型別約束也可以用在泛型類別的定義中。請注意,無型別約束一定也要和其它型別參數一起在尖括號中宣告:

//naked type constraint

##public class MyClass where T : V

因為編譯器只認為無型別約束是從System.Object繼承而來,所以帶有無型別約束的泛型類的用途十分有限。當你希望強制兩個型別參數具有繼承關係時,可對泛型類別使用無型別約束。

五、泛型類別

泛型類別封裝了不針對任何特定資料類型的運算。泛型類別常用於容器類,如鍊錶、雜湊表、棧、佇列、樹等等。這些類別中的操作,如對容器新增、刪除元素,不論所儲存的資料是何種類型,都執行幾乎相同的操作。

通常,從一個已有的具體類別來建立泛型類,並每次把一個類型改為類型參數,直到達到一般性和可用性的最佳平衡。當建立你自己的泛型類別時,需要重點考慮的事項有:

  • 哪些類型應泛化為類型參數。一般的規律是,用參數表示的類型越多,程式碼的彈性和多用性就越大。過多的泛化會導致程式碼難以被其它的開發人員理解。

  • 如果有約束,那麼類型參數需要什麼樣約束。一個好的習慣是,盡可能使用最大的約束,同時確保可以處理所有需要處理的類型。例如,如果你知道你的泛型類別只打算使用引用類型,那麼就應用這個類別的約束。這樣可以防止無意中使用值類型,同時可以對T使用as運算符,並且檢查空引用;

  • #把泛型行為放在基底類別中還是子類別中。泛型類別可以做基底類別。同樣非泛型類別的設計中也應考慮這一點。泛型基底類別的繼承規則;

  • 是否實作一個或多個泛型介面。例如,要設計一個在基於泛型的容器中建立元素的類,可能需要實作類似IComparable的接口,其中T是該類別的參數。

對於一個泛型類別Node,客戶程式碼既可指定一個型別參數來建立一個封閉建構型別(Node),也可保留型別參數未指定,例如指定一個泛型基底類別來建立開放式建構型別(Node)。泛型類別可以繼承自具體類別、封閉建構型別或開放建構型別:

// concrete type

class Node : BaseNode
//closed constructed type
class Node : BaseNode
//open constructed type
class Node : BaseNode
#class Node : BaseNode

##class

非泛型的具體類別可以繼承自封閉建構基類,但不能繼承自開放建構基類。這是因為客戶程式碼無法提供基底類別所需的類型參數:

#//No error.

class Node : BaseNode//Generates an error.
class Node : BaseNode

泛型的具體類別可以繼承自開放構造類型。除了與子類別共用的類型參數外,必須為所有的類型參數指定類型:

#//Generates an error.

class Node : BaseNode {…}
//Okay.
class Node : BaseNode {…}

繼承自開放結構類型的泛型類,必須指定參數類型與限制:

class NodeItem where T : IComparable, new() {…}

class MyNodeItem : NodeItem where T : IComparable, new() {…}##, new() {…}

#泛型類型可以使用多種型別參數與限制:

class KeyType {…}
class SuperKeyType where U : IComparable, where V : new() {…}



###########################################################################################################################################################################################################4 ###########

開放結構與封閉建構型別可以用來當作方法的參數:

#void Swap(List list1, List list2) {…}
void Swap(List list1, List list2) {…}

六、泛型介面

當一個介面被指定為型別參數的限制時,只有實作該介面的型別可被用作型別參數。

可以在一個型別指定多個介面作為約束,如下:






class Stack, IMyStack1{}

##一個介面可以定義多個型別參數,如下:

IDictionary##介面與類別的繼承規則相同:

//Okay.IMyInterface: IBaseInterface
//Okay.

IMyInterface
//Okay.
IMyInterface: IBaseInterface
//Error.

IMyInterface

##特定類別可以實作封閉建構接口,如下:

class MyClass : IBaseInterface

#泛型類別可以實作泛型介面或封閉建構接口,只要類別的參數清單提供了接口所需的所有參數,如下:





//Okay.
class MyClass : IBaseInterface
// Okay.
class MyClass : IBaseInterface

泛型類別、泛型結構,泛型介面都具有同樣方法重載的規則。

 

七、泛型方法


泛型方法是聲名了型別參數的方法,如下:

void Swap(ref T lhs, ref T rhs){##void Swap(ref T lhs, ref T rhs)

{     T temp;     temp = lhs;      lhs = rhs;}
     rhs = temp;

下面的示例代碼顯示了一個以int作為型別參數,來呼叫方法的範例:

int a = 1;

int b = 2;
// …
Swap(a, b);

#也可以忽略型別參數,編譯器會去推斷它。下面呼叫Swap的程式碼與上面的範例等價:

#Swap(a, b);在泛型類別中,非泛型方法能存取所在類別中的型別參數:





靜態方法和實例方法有著相同的類型推斷規則。編譯器能夠根據傳入的方法參數來推斷類型參數;而無法單獨根據約束或傳回值來判斷。因此類型推斷對沒有參數的方法是無效的。類型推斷發生在編譯的時候,且在編譯器解析重載方法標誌之前。編譯器對所有同名的泛型方法套用類型推斷邏輯。在決定(resolution)重載的階段,編譯器只包含那些型別推斷成功的泛型類別。

########## ##class List###{###     void Swap(ref T lhs, ref T rhs) { ... }###}################## ########在泛型類別中,定義一個泛型方法,和其所在的類別具有相同的類型參數;試圖這樣做,編譯器會產生警告CS0693。 #####################class List###{###     void Swap(ref T lhs, ref T rhs) {  }# ##}#########warning CS0693: 類型參數「T」與外部型別「List」中的型別參數同名###############

在泛型類別中,定義一個泛型方法,可定義一個泛型類別中未定義的型別參數:(不常用,一般配合約束使用)

#class List
{
     void Swap(ref T lhs, ref T rhs) {  }   //不常用者對 v Add(List items) where U : T{}  //常用

}


泛型方法經過多個類型參數來重載。例如,以下的這些方法可以放在同一個類別中:

#void DoSomething() { } 
void DoSomething( ) { }

void DoSomething() { }


八、泛型中的default關鍵字

在泛型類別和泛型方法中會出現的一個問題是,如何把預設值賦給參數化類型,此時無法預先知道以下兩點:

  1. T將是值型別還是引用型別

  2. #如果T是值型,那麼T將是數值還是結構

對於一個參數化類型T的變數t,僅當T是引用型別時,t = null語句才是合法的; t = 0只對數值的有效,對結構則不行。這個問題的解決方法是用default關鍵字,它對參考類型傳回空,對值類型的數值類型傳回零。而對於結構,它將傳回結構每個成員,並根據成員是值類型還是引用類型,傳回零或空。

class GenericClass{
     T GetElement()
     {
   #    }
}


以上是泛型的概述與具體使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板