《C#進階編程》【第五章】泛型,
泛型是進階程式設計語言的一種特性。泛型的引入使得程式開發的效率得到提高,代碼的重用性大大的提升。有了泛型,我們可以建立獨立於被包含類型的類和方法,我們不必給不同的類編寫功能相同的很多方法或者類,只建立一個方法或類就可以了。現在我們看看泛型的優點
效能上,泛型不需要進行類型轉換(也就是拆箱和裝箱)。
型別安全,和Object類相比,Object類屬於非型別安全的,而泛型使用泛型型別,可以根據需要用特定的類型替換泛型型別,這樣就保證了型別安全類。在編譯時間,只要與泛型型別T定義的允許使用的類型不同,編譯器就會報錯,這樣我們就可以儘早的發現錯誤。
二進位代碼重用,在C#中可以一次定義多次使用,然而C++卻要不斷的訪問源碼。
代碼的擴充,對於參考型別,他們將會共用本地類的所有相同代碼。只有值類型,才會每次執行個體化一個新類。
我們看完了泛型的優點,我們現在可以來看看泛型具體怎麼使用。我們先看一幅圖,瞭解一下泛型的組成。
1、泛型類
我們先看看泛型型別的命名方法:<1>一大寫字母T開頭 <2>如果泛型型別沒有特定的要求,或者使用了兩個、兩個以上的泛型型別,就需要給出描述性的名稱。(如:TValue,TKey等等)。
現在我們看看泛型類的聲明文法:
[存取權限修飾符] class 類名<T>{//類體}
類名的T就是泛型型別,具體類型執行個體化時給出。然而在類體中就可以直接將T當成一個具體的類型來使用。每個處理物件類型的類都可以有泛型實現方式。如果類使用了階層,那麼泛型就可以很好的消除類型轉換的操作。
注意:T兩邊的’<’和’>’不能少。
在建立泛型類時,我們還需要考慮一些問題。首先我們應該怎麼初始化成員變數呢?然而我們並不知道究竟是參考型別還是值類型,為瞭解決這個問題我們就引入了default關鍵字。文法如下:
T value = default(T)
這樣我們就圓滿的解決成員變數初始化的問題。default會給值類型賦值為0,參考型別賦值為null。
如果泛型類需要調用泛型型別的方法,那麼就必須添加約束。那麼什麼是約束呢?約束就是讓泛型型別只能使用我們所約束的類型。我們使用where關鍵字來聲明約束。公有以下6種約束:
<1>where T : struct 結構約束,類型T必須為值類型
<2>where T : class 參考條件約束,類型T必須是參考型別
<3>where T : IFoo 介面約束,類型T必須實現介面IFoo
<4>where T : Foo 類約束,類型T必須派生自基類Foo
<5>where T : new() 類型T必須有一個預設的建構函式
<6>where T1 : T2 裸型約束,類型T1派生自泛型T2。
對於裸型約束,我們來具體說說吧。我們舉個例子:
public class Test<T1, T2> where T1 : T2{//類體}
我們重點看看執行個體化的時候
var MyTest = new Test<Class1, Class2>();
那麼此時,Class1類就必須派生自Class2類,否則編譯器就會報錯。
使用泛型型別還可以組合多個約束,例如:
public class MyClass<T> where T : IFoo, new(){//類體}
泛型類,既然作為類那麼它必然可以繼承。泛型類可以實現泛型介面,也可以派生自泛型基類。例:
public class Myclass<T> : IDD<T>{//類體}或者這樣
public class Base<T>{}public class MySub<T>: Base<T>{}
也可以是這樣:
public class MySub<T>: Base<string>{}
衍生類別可以是泛型類還可以是非泛型類。
public classMysub: Base<int>{}
泛型類,比較特別的應該就是它的靜態成員了。因為泛型類的靜態成員只能在類的一個執行個體中共用。假設類MySub<T>存在靜態欄位x,那麼如果同時對一個int型和string型使用了Mysub<T>類,所以就存在兩組欄位:Mysub<int>.x和MySub<string>.x
2、泛型介面
看到泛型介面,我們就要提到,抗變與協變。現在我們就看看什麼抗變與協變。
假設:TSub是TParent的子類。
協變:如果一個泛型介面IFoo<T>,IFoo<TSub>可以轉換為IFoo<TParent>的話,我們稱這個過程為協變,IFoo支援對參數T的協變。
逆變:如果一個泛型介面IFoo<T>,IFoo<TParent>可以轉換為IFoo<TSub>的話,我們稱這個過程為逆變,IFoo支援對參數T的逆變。
泛型型別的協變用關鍵字out聲明,泛型介面就是協變的,這就意味著其傳回型別只能是T。
public interface IIndex<out T>{T this[int index] {get ;}}
泛型型別的抗變用關鍵字in聲明,泛型介面就是抗變的。這樣泛型型別T就只能作為方法輸入。例如:
public interface IDisplay<in T>{void Show(T test);}
通常只有具備繼承關係的對象才可以發生隱式類型轉換,如Base b=new sub()。
協變和逆變可以使得更多的類型之間能夠實現隱式類型轉換、型別安全得到保障。
3、泛型結構
其實泛型結構和泛型類幾乎是一致的,只是泛型結構沒有繼承的特性。.Net平台提供的一個泛型結構是(可空類型)Nullable<T>。可空類型的引入,主要是為瞭解決資料庫語言中的數字與程式設計語言中的數位區別(資料庫中數字可以為空白,程式設計語言中數字不可為空白)。因為Nullable<T>使用過於的繁瑣,於是我們就引入了一種特殊的文法,使用‘?’運算子。例:
int? x1;Nullable<int> x2;
在上面的例子中,x1與x2這兩種方式定義是等價的。
非空類型可以轉化為可空類型。(總是成功的且可以隱式轉化)
可空類型可以轉化為非空類型。當可空類型的值為null時就會拋出異常。(需要顯示轉化)
如果不進行顯示轉化,我們就可以使用”??”運算子。如下:
int? x1 = GetNullableType();int y1 = x1 ?? 0;
這樣的話,當x1為null時,就會賦給y1一個0。
4、泛型方法
除了可以定義泛型類,泛型也是可以定義成方法的。文法如下:
[傳回值類型] 方法名稱<T>(參數列表){} 其餘的用法就和普通方法是一樣的,唯一的區別就是它有一個通用類型T。
泛型方法和類一樣,也可以加約束,具體約束的方法和泛型類是一樣的。文法如下:
[傳回值類型] 方法名稱<T>(參數列表) where T : [約束方式]{}
以上就是泛型的內容了