泛型的概述和具體使用

來源:互聯網
上載者:User

一、泛型概述

泛型類和泛型方法兼複用性、型別安全和高效率於一身,是與之對應的非泛型的類和方法所不及。泛型廣泛用於容器(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<T>容器裡,同樣是把元素加入容器的操作,類似這樣:

List<int> listInt = new List<int>();
listInt.Add(100);
listInt.Add(200);
listInt.Add(123.112); //編譯報錯
listInt.Add("heel"); //編譯報錯

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

與ArrayList相比,在客戶代碼中唯一增加的List<T>文法是聲明和執行個體化中的型別參數。代碼略微複雜的回報是,你建立的表不僅比ArrayList更安全,而且明顯地更加快速,尤其當表中的元素是實值型別的時候。

三、泛型的型別參數

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

List<int> listInt = new List<int>();
List<float> listFloat = new List<float>();
List<String> listString = new List<String>();

四、泛型型別參數的約束

泛型提供了下列五種約束:

約束 描述
where T : struct 參數類型必須為實值型別
where T : class 參數類型必須為參考型別
where T : new() 參數類型必須有一個公有的無參建構函式。當與其它約束聯合使用時,new()約束必須放在最後。
where T : <Base Class Name> 參數類型必須為指定的基底類型或派生自指定基底類型的子類
where T : <Interface Name> 參數類型必須為指定的介面或指定介面的實現。可指定多個介面的約束。介面約束也可以是泛型的。

無限制型別參數:

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

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

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

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

class List<T>
{
void Add<U>(List<U> items) where U : T
{
}
}

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

無類型約束也可以用在泛型類的定義中。注意,無類型約束一定也要和其它型別參數一起在角括弧中聲明:

//naked type constraint

public class MyClass<T,U,V> where T : V

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

五、泛型類

泛型類封裝了不針對任何特定資料類型的操作。泛型類常用於容器類,如鏈表、雜湊表、棧、隊列、樹等等。這些類中的操作,如對容器添加、刪除元素,不論所儲存的資料是何種類型,都執行幾乎同樣的操作。

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

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

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

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

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

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

// concrete type
class Node<T> : BaseNode
//closed constructed type
class Node<T> : BaseNode<int>
//open constructed type
class Node<T> : BaseNode<T>

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

//No error.
class Node : BaseNode<int>
//Generates an error.
class Node : BaseNode<T>

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

//Generates an error.
class Node<T> : BaseNode<T, U> {…}
//Okay.
class Node<T> : BaseNode<T, int> {…}

繼承自開放結構類型的泛型類,必須指定參數類型和約束:

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

泛型型別可以使用多種型別參數和約束:

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

開放結構和封閉構造類型可以用作方法的參數:

void Swap<T>(List<T> list1, List<T> list2) {…}
void Swap(List<int> list1, List<int> list2) {…}

六、泛型介面

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

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

class Stack<T> where T : IComparable<T>, IMyStack1<T>{}

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

IDictionary<K,V>

介面和類的繼承規則相同:

//Okay.
IMyInterface: IBaseInterface<int>
//Okay.
IMyInterface<T> : IBaseInterface<T>
//Okay.
IMyInterface<T>: IBaseInterface<int>
//Error.
IMyInterface<T> : IBaseInterface2<T, U>

具體類可以實現封閉構造介面,如下:

class MyClass : IBaseInterface<string>

泛型類可以實現泛型介面或封閉構造介面,只要類的參數列表提供了介面需要的所有參數,如下:

//Okay.
class MyClass<T> : IBaseInterface<T>
//Okay.
class MyClass<T> : IBaseInterface<T, string>

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

七、泛型方法

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

void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}

下面的範例程式碼顯示了一個以int作為型別參數,來調用方法的例子:

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

也可以忽略型別參數,編譯器會去推斷它。下面調用Swap的代碼與上面的例子等價:

Swap(a, b);

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

在泛型類中,非泛型方法能訪問所在類中的型別參數:

class List<T>
{
void Swap(ref T lhs, ref T rhs) { ... }
}

在泛型類中,定義一個泛型方法,和其所在的類具有相同的型別參數;試圖這樣做,編譯器會產生警告CS0693。

class List<T>
{
void Swap<T>(ref T lhs, ref T rhs) { }
}

warning CS0693: 型別參數“T”與外部類型“List<T>”中的型別參數同名

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

class List<T>
{
void Swap<U>(ref T lhs, ref T rhs) { } //不常用

void Add<U>(List<U> items) where U : T{} //常用
}

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

void DoSomething() { }
void DoSomething<T>() { }
void DoSomething<T, U>() { }

八、泛型中的default關鍵字

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

  1. T將是實值型別還是參考型別

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

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

class GenericClass<T>
{
T GetElement()
{
return default(T);
}
}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.