上一篇對.NET中的泛型進行了詳細的介紹以及使用泛型的好處是什麼,這篇將更加深入的去了解泛型的其他的知識點,重頭戲.
【1】泛型方法
上一篇我們也說過了,泛型可以是類,結構,介面,在這些泛型型別中定義的方法都可以叫做泛型方法,都可以引用由泛型型別本身指定的一個型別引數例如:
1 2 3 4 5 6 7 8 |
public class GenericType<T> { private T G_Value; public T Convert<T> { T res = (T)Convert.ChangeType(G_value,typeof(T)); return res; } } |
泛型方法的存在為我們提供了極大的靈活性。
泛型方法顧名思義,首先它是一個方法,只不過這個方法的返回值,引數可能是泛型的,
型別推斷
型別推斷就是根據泛型方法中定義的形參型別T,在我們呼叫的使用,那麼所要傳的也應該是一致的。例如:
1 2 3 4 5 6 |
static void Test<T >(ref T t1,ref T t2) { T temp = t1; t1 = t2; t2 = temp; } |
這是我們定義的一個泛型方法,第一個引數型別為 T,第二個也為 T,說明這兩個型別是一致的。那麼我們在呼叫的時候應該這樣 :
1 2 3 |
int i = 8, j = 0 ; Test( ref i, ref j); |
而如果我們兩個實參的型別不統一的話,那麼就會引發編譯異常例如 :
開放型別和封閉型別
具有泛型型別引數的型別物件讓就還是一個型別, CLR內部依舊和其他的物件型別一樣建立一個內部型別物件, 那麼這種具有泛型型別引數的型別稱之為開放型別,開發型別不能進行構造任何的例項.
當泛型型別引數是一個實參時,那麼所有的型別實參傳遞的都是實際的資料型別了,這種型別稱之為 封閉型別,封閉型別是允許構造例項物件的
1 2 3 4 5 6 7 8 9 10 11 12 |
class DictionaryStringKey <T> : IEnumerable<T> { public IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); } System .Collections. IEnumerator System. Collections.IEnumerable .GetEnumerator() { throw new NotImplementedException(); } } |
在上述程式碼中,有一個DictionaryStringKey的類,繼承了一個IEnumable<T>的泛型,那麼根據我們的開放型別它在呼叫的時候是不只是具體的實參,並且不能被構造例項的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
static void Main( string[] args) { object o = null; Type t = typeof( DictionaryStringKey <>); o = CreateInstance(t); Console .ReadLine(); } private static object CreateInstance( Type t) { object o = null; try { o = Activator . CreateInstance(t); Console .WriteLine( @" 建立了 " + t + "的例項 " , t .ToString()); } catch (Exception ex) { o = Activator . CreateInstance(t); Console .WriteLine( @" 建立 " + t + "的例項失敗 " , t .ToString()); } return o; } |
上述程式碼中我嘗試著對開放型別進行例項化,結果報出異常 :
當我們傳入實參之後,那麼它就是一個封閉型別,這樣我們就可以進行構造例項了.
1 2 3 4 5 6 7 8 |
static void Main( string[] args) { object o = null; Type t = typeof( DictionaryStringKey <string > ); o = CreateInstance(t); Console .ReadLine(); } |
【2】泛型介面
泛型的主要作用就是定義泛型的引用型別和值型別,在CLR中,對於泛型介面的支援也是很重要的,因為這樣更有益與程式的擴充套件性, 另外一點如果沒有泛型介面我們每次使用非泛型介面時都會進行裝箱操作(例如: IComparable),並且會失去編譯時的安全性(因為它是非泛型的,較之泛型的好處之一).
那麼CLR提供了對於泛型介面的支援,對於引用型別和值型別可以通過制定實參的方式來實現泛型介面,同時也可以保持未指定狀態來實現一個泛型介面( 接受者也可以是泛型 , 可以在後期進行指定型別)
例項 1 : 宣告一個泛型介面
1 2 3 4 5 |
interface IPair <T> { T First { get; set ; } T Second { get; set ; } } |
IPair這個介面實現了一對相關的物件,兩個資料項具有相同的型別.
實現介面時,語法與非泛型類的語法是相同的,如果在實現泛型介面時,同時不指定實參,則預設為是一個泛型類.
例項 2 : 泛型介面的實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Pair <T> : IPiar< T> { public T First { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public T Second { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } } |
CLR泛型介面的支援對於集合型別來講尤其重要,同時泛型在集合型別中較為常用,如果沒有泛型那麼我們所使用List的時候都要依賴與System.Collections,我們在每次訪問的時候都需要執行一次轉換,如果使用泛型介面,就可以避免執行轉型,因為引數化的介面能實現更強的編譯時繫結.
例項 3 : 在一個類中多次實現相同的介面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
interface IContainer <T> { ICollection<T > Items { get ; set; } } public class Person : IContainer<A >,IContainer <B> { public ICollection <A> Items { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } ICollection<B > IContainer <B>. Items { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } } |
Items屬性使用一個顯式介面實現多次出現,每一次,型別引數都有所不同,如果沒有泛型,這是做不到的,在非泛型的情況下,CLR只允許一個顯式的IContainer.Items屬性.
【3】泛型約束(主要約束,次要約束,構造器約束)
約束也可以理解為是一種規則,意思就是我們所編寫的所有泛型,你在呼叫的時候,應該給我傳輸那些型別的實參。
在上一篇中,我們也在一個DataTableToList中使用到了約束,但是並沒有進行講解,在這裡我們詳細的說明一下:
在.NET 中有4中約束方式,它們的常規語法是相同的,約束要放在泛型方法或者泛型型別宣告的末尾,並由上下文關鍵字 ”where :“ 來引入 。
主要約束(引用型別約束):
主要約束表示的是 : 我們在指定一個引用型別約束時,那麼一個指定的型別實參要麼是與約束型別相同的型別,要麼是從約束型別派生的一個型別
例項 4 : 引用型別約束
1 |
struct Generic<T>(T t1) where : class |
這裡我定義了一個泛型方法,Generic,後來寫了一個where : class,這就告訴編譯器,在使用者呼叫這個方法時,必須為這個方法的傳入一個引用型別的實參,同時,我們也可以注意到這個方法的返回型別為struct值型別。
主要約束(值型別約束)
很明顯,值型別約束的話,就是將 約束條件指定為 Struct
例項 5 : 值型別約束
1 2 3 4 5 6 7 |
class ConstraintOfStruct <T> where T : struct { public T result() { return new T(); } } |
在程式碼中,我們最後返回的是new T(),這個是沒有問題的,因為我們已經可以肯定T就是值型別,所有的值型別都有一個隱式的建構函式,那麼如果我們將 約束條件改為 class的話,那麼就會出異常,因為不是所有的引用型別都可以例項化.
例項 6 : 反面教材 – 約束為引用型別時進行例項化.
建構函式型別約束
建構函式型別約束表示為 T : new(),這個約束必須為所有的型別引數的最後一個約束,它用於檢查型別實參是否有一個可用於建立型別例項的無參建構函式,這一點比較適用於所有的值型別,所有的沒有顯式宣告建構函式的非靜態、非抽象類,所有顯式宣告瞭一個公共無參建構函式的非抽象類。
例項 7 : 建構函式型別約束
1 2 3 4 |
public T CreateInatance<T >() where T : new() { return new T(); } |
在方法createInstance中,要求我們必須傳入一個無參的建構函式,例如:我們可以傳入 object,struct,stream等,但是不能傳入 string,Directory等.
介面約束:
介面約束的一個好處就是我們可以指定多個介面,但是隻能指定一個類
1 2 3 |
class ConstraintOfStruct <T> where T : class,IEquatable <T> ,IComparable<T> { } |
組合約束 :
組合約束就是指定多個約束條件在上一個約束中我們已經看到了 ,更詳細的如下 :
1 2 3 4 5 |
class Sample<T> where T : class ,IDisposable,new(){} class Sample<T> where T : struct,IDisposable{} class Sample<T,U> where T : Stream where U : IDisposable{} class Sample<T> where T : class ,struct |
【4】泛型型別轉型
1 2 3 4 |
public static T Desercialies <T > (Stream stream, IFormatter formatter) { return (T)formatter. Deserialize(stream); } |
formatter 負責將流轉換為 Object,返回一個Object型別,我們在使用泛型呼叫的時候應該這樣來 :
1 |
string result = Desercialies <string > (stream, formatter); |
上述的程式碼看著是一種強制轉換,但是仍然執行了隱式的轉換,就和使用非泛型 string result = (string)Desercialies(stream, formatter); 一樣
【5】泛型和屬性
屬性也可以應用到泛型型別中,使用的方式和非泛型型別相同.自定義的屬性只允許引用開發泛型型別
1 2 3 4 5 6 7 8 |
class MyClass<T,S> where T :class where S : struct { public T MyName { get; set; } public S MyCode { get; set; } public List<T> MyCourse { get; set; } public List<S> MyStudyHistory { get; set; } } |
上述我們定義了一個MyClass 類,這個類有兩個引數一個是引用型別一個是值型別.在屬性MyName,MyCourse的型別都是引用型別,MyCode的型別都為值型別
泛型型別構造器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Pair < T> : IPiar< T > { private T _first; public T First { get { return _first; } set { _first = value; } } private T _second; public T Second { get { return _second; } set { _second = value; } } public Pair(T first,T second) { } } interface IPiar < T> { } |
泛型的構造器不需要新增型別引數來與類的宣告一致.
先到這裡, 下一篇著重分享協變和逆變,否則這一篇符太長,不利於以後的檢視.
泛型委託和泛型反射留在深入總結委託和反射的時候進行總結。
同時,希望給在閱讀的你帶來一些幫助。