《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 : [約束方式]
{
}

以上就是泛型的內容了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章