泛型約束

一、泛型簡介
1.1泛型
通過使用泛型,可以創建這樣的類、接口和方法,它們以一種類型安全的工作方式操作各種數據。
本質上,術語“泛型”指的是“參數化類型”(parameterized types)。參數化類型非常重要,因爲它們可以在創建類、接口、方法和委託的時候將要操作的數據類型作爲參數進行指定。
通過泛型,可以創建一個類,使其自動處理不同類型的數據。使用參數化類型的類、接口、方法和委託都可以稱爲“泛型”。
可以通過使用object類型的引用來創建通用的類、接口、方法和委託。但這種方法的缺陷在於,無法保證類型安全。泛型彌補了無法保證類型安全的缺陷,也簡化了處理的過程。因爲不再需要執行object與實際操作的數據類型之間的轉換。
因此,泛型可以使開發人員最大限度地重用代碼,並使得代碼更加簡單,更加安全。

泛型類型因類型參數的不同而不同。
理解泛型類型的一個關鍵在於,對某個泛型類型的一個版本的引用並不能與同一泛型類型的另一個版本相兼容。

1.2泛型如何實現類型安全?
泛型可以自動確保有關的泛型類的所有操作都是類安全的。在處理過程中,泛型避免了手工進行轉換和編寫類型檢查代碼的必要。

1.3泛型類的通用形式:
class class-name<type-param-list>{}
聲明一個對泛型類的引用的語法:
class-name<type-arg-list> var-name=new class-name<type-arg-list>(cons-arg-list);
舉例:
class GenericClass<T, V>
{
        T data;//declare an object of type T
        V data2;      
        public GenericClass(T obT, V ob2)
        {
            data1 = ob1;
            data2 = ob2;
        }
        public T GetData()
        {
            return this.data;
        }
        public void ShowType()
        {
            Console.WriteLine("The type of T is : {0},The type of V is:{1}",typeof(T),typeof(V));
        }
}

在實際應用中,我們往往會對類型T進行一定的約束,以限制應用區域。

二、類型約束(Constrained types)

2.1類型約束(Constrained types)
在指定一個類型參數時,可以指定類型參數必須滿足的約束條件。這是通過在指定類型參數時使用where子句來實現的。
class class-name<type-param> where type-param:constraints{}
其中constraints是一個逗號分割的約束列表。

2.2五種約束類型
(1)可以使用“基類約束”(base class constraint)來指定某個基類必須出現在類型實參中。這種約束是通過指定基類名稱來實現的。
(2)可以使用“接口約束”(interface constraint)來指定某個類型實參必須實現一個或多個接口。這種約束是通過指定接口名稱來實現的。
(3)可以要求類型實參必須提供一個無參數的構造函數,這被稱爲“構造函數約束”(constructor constraint)。它是通過new()指定的。
(4)可以通過關鍵字class指定“引用類型約束”(reference type constraint)來限制某個類型實參必須是引用類型。
(5)可以通過關鍵字struct指定“值類型約束”(vlaue type constraint)來限制某個類型實參必須是值類型。
2.2.1 基類約束
使用基類約束,我們可以指定某個類型實參必須繼承的基類。
基類約束有兩個功能:
(1)它允許在泛型類中使用由約束指定的基類所定義的成員。例如,可以調用基類的方法或者使用基類的屬性。如果沒有基類約束,編譯器就無法知道某個類型實參擁有哪些成員。通過提供基類約束,編譯器將知道所有的類型實參都擁有由指定基類所定義的成員。
(2)確保類型實參支持指定的基類類型參數。這意味着對於任意給定的基類約束,類型實參必須要麼是基類本身,要麼是派生於該基類的類,如果試圖使用沒有繼承指定基類的類型實參,就會導致編譯錯誤。
基類約束使用下面形式的where子句:
where T:base-class-name
T是類型參數的名稱,base-class-name是基類的名稱,這裏只能指定一個基類。

2.2.2 接口約束
接口約束用於指定某個類型參數必須應用的接口。接口的兩個主要功能和基類約束完全一樣。
基本形式 where T:interface-name
interface-name是接口的名稱,可以通過使用由逗號分割的列表來同時指定多個接口。
如果某個約束同時包含基類和接口,則先指定基類列表,再指定接口列表。

2.2.3 new()構造函數約束
new()構造函數約束允許開發人員實例化一個泛型類型的對象。
一般情況下,我們無法創建一個泛型類型參數的實例。然而,new()約束改變了這種情況,他要求類型參數必須提供一個無參數的構造函數。
在使用new()約束時,可以通過調用該無參構造函數來創建對象。
基本形式: where T : new()
使用new()約束時應注意兩點:
(1)它可以與其他約束一起使用,但是必須位於約束列表的末端。
(2)new()僅允許開發人員使用無參構造函數來構造一個對象,即使同時存在其他的構造函數。換句話說,不允許給類型參數的構造函數傳遞實參。

2.2.4 引用類型和值類型約束
如果引用類型和值類型之間的差別對於泛型代碼非常重要,那麼這些約束就非常有用。
基本形式:
where T : class
where T : struct
若同時存在其他約束的情況下,class或struct必須位於列表的開頭。

另外可以通過 使用約束來建立兩個類型參數之間的關係
例如 class GenericClass2<T, V> where V:T{} -------- 要求V必須繼承於T,這種稱爲裸類型約束(naked type constraint)

舉例:
class BaseC
{
        int baseInt;
        string baseStr;
        public void Show()
        {
            Console.WriteLine("Something");
        }
}
//基類約束
class GenericClass3<T> where T : BaseC
{
        T ob1;
        public void Show()
        {
            ob1.Show();
        }
}
//new()構造函數約束
class GenericClass4<T> where T : new()
{
        public T ob1;
        public GenericClass4()
        {
            ob1 = new T();
        }
}

2.2.5 default 關鍵字
泛型代碼中的默認關鍵字(C# 編程指南)
在泛型類和泛型方法中產生的一個問題是,在預先未知以下情況時,如何將默認值分配給參數化類型 T:

T 是引用類型還是值類型。

如果 T 爲值類型,則它是數值還是結構。

給定參數化類型 T 的一個變量 t,只有當 T 爲引用類型時,語句 t = null 纔有效;只有當 T 爲數值類型而不是結構時,語句 t = 0 才能正常使用。解決方案是使用 default 關鍵字,此關鍵字對於引用類型會返回空,對於數值類型會返回零。對於結構,此關鍵字將返回初始化爲零或空的每個結構成員,具體取決於這些結構是值類型還是引用類型。

 

三、使用泛型類

在給泛型類傳遞類型實參的時候,實際創建的是C#中的“封閉構建類型”(closed constructed type)。
術語“封閉”一詞,是指指定了類型實參,因此GenericClass<int>是一個封閉構建類型。
本質上,泛型類型,例如GenericClass<T>,是一種抽象結構。只有在特定的版本(例如GenericClass<int>)被構建以後才創建了一個實際的類型。
在C#中術語中,GenericClass<T>之類的構造被稱爲“開放構建類型”(open constructed type),是因爲他沒有指定類型實參。
舉例:
public void Demo1()
{
       GenericClass<int,string> gen=new GenericClass<int,string>(12,"I am Chinese");
       Console.Write("Type: ");
       gen.ShowType();
       Console.WriteLine("The first Data: " + gen.GetData());
}
//new()構造函數約束
public void Demo2()
{
       GenericClass4<BaseC> demo4 = new GenericClass4<BaseC>();
       demo4.ob1.Show();
}

總結:

①:default

      在泛型中,要爲某個使用泛型的變量初始化值,可是我們需要考慮的是這個泛型可能是引用類型,也可能是值類型,這時我們可以藉助default來完成初始化複製。T value = default(T);如果T是引用類型,value = null,如果是T是值類型,value = 0.

②:where

                                       約束                                                                  說明

                                where T:struct                                     使用結構約束,類型T必須是值類型

                                where T:calss                                       類約束指定,類型T必須是引用類型

                                where T:IFoo                                            指定類型T必須執行結構IFoo

                                where T:Foo                                            指定類型T必須派生於基類Foo

                               where T:new()                                       指定類型T必須有一個默認構造函數 

                                 where T:U                                          類型T派生於泛型類型V(裸類型約束)

   (注:在CRL2.0中,只能爲默認構造函數定義約束,不能爲其他構造函數定義約束)

 四.泛型繼承

 如何實現C#泛型類的繼承呢?這裏需要滿足下面兩點中的任何一點即可:

1、泛型類繼承中,父類的類型參數已被實例化,這種情況下子類不一定必須是泛型類;

2、父類的類型參數沒有被實例化,但來源於子類,也就是說父類和子類都是泛型類,並且二者有相同的類型參數;

//如果這樣寫的話,顯然會報找不到類型T,S的錯誤  
public class TestChild : Test< T, S> { }  

//正確的寫法應該是  

public class TestChild : Test< string, int>{ }  

public class TestChild< T, S> : Test< T, S> { }

發佈了112 篇原創文章 · 獲贊 5 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章