第十二章 泛型
-
泛型:支持泛型值類型、泛型引用類型、泛型接口、泛型委托;允許在引用類型、值類型和接口中定義泛型方法;泛型參數變量要么稱為T,要么以T開頭
-
具有泛型類型參數的類型稱為開放類型,不允許構造實例;當為所有參數類型傳遞了實際參數,類型就稱為封閉類型,允許構造實例
-
泛型類型是類型,所以可以從其他類型派生;使用泛型類型并指定形參時,實際是定義一個新的類型(從泛型類型派生自的類型派生)。例:List<T>派生自Object,所以List<string>也派生自Object
-
泛型的同一性(不想讓符號<>降低可讀性時的處理)
- 使用class dtl : List<DateTime>,會失去同一性(typeof(dtl) != typeof( List<DateTime>))
- 可以使用 using dtl = System.Collections.Generic.List<DateTime>
- 可以直接var dtl = new List<DateTime>
-
代碼爆炸(CLR每新出現一種類型的實參都要編譯一次代碼)
- 不同程序集中同一類型只編譯一次(限定同一個domain)
- 所有引用類型都視為同一種實參(所有引用類型只編譯一次)
-
可以定義泛型接口,如IEnumerator<T>
-
可以定義泛型委托,如Action<T>
-
泛型類型參數的逆變和協變
-
逆變性:用in關鍵字標記的泛型參數,可以更改為它的派生類(輸入)
-
協變性:用out關鍵字標記的泛型參數,可以更改為它的基類(輸出)
-
例:定義public delegate TResult Func<in T, out TResult>(T arg);
Func<Object, ArgumentException> fn1 = null,
Func<String, Exception> fn2 = fn1 是合法的(因為String從Object派生,Exception是ArgumentException的基類)
使用:Exception e = fn2(" ") ; (實參String->Object, 返回值ArgumentException->Exception)
-
委托和接口都可以將類型參數標記為逆變量和協變量
-
-
泛型方法,定義泛型類、結構或接口時,類中定義的方法可以使用類指定的類型參數
例:定義了class SomeClass<T>, 類中的方法可以使用類指定的T,如定義public T SomeMethod<T>()
-
由于out/ref實參傳遞的變量必須具有與方法參數相同的類型,所以可以使用泛型實現。
例:private static void Swap<T>(ref T o1, ref T o2);
-
C#編譯器在調用泛型方法時可以進行類型推斷,在可以確定類型實參的情況下可以不用<>指定
-
屬性、索引器、事件、操作符方法、構造器、終結器本身不能有類型參數,但是可以在泛型類中定義,從而使用泛型類的類型參數
-
泛型可驗證性:編譯代碼時需要確定代碼適用于當前或已有或將來可能定義的任何類型,比如調用T.ToString(),對所有類型都有ToString()方法,所以代碼合法
-
使用where關鍵字指定約束
- 約束可以應用于泛型類和泛型方法,不能用于重載(不能基于類型參數名稱或約束重載,也不允許為重寫方法的類型指定約束)
- 主要約束:主要約束指非密封類的一個引用類型(不包括Object, Array, Delegate, MulticastDelegate, ValueType, Enum, Void),能有零個或一個
- 特殊主要約束:class,指引用類型;struct,指值類型
- 次要約束:指接口類型,能有零個或多個
- 構造器約束:指擁有公共無參構造器的非抽象類型
-
其他可驗證性問題
- 不能將泛型變量轉型為其他變量(除非轉成與約束兼容的類型)
- 不能將泛型變量設為null(除非泛型類型被約束成引用類型)
- 可以將泛型類型變量與null比較(==或!=),(雖然值類型不能與null比較(因為永遠不會null),但是約束成值類型后,代碼能通過編譯,但不執行if邏輯,只生成永遠為true的分支的代碼)
- 同一泛型類型的兩個變量,如果不能肯定是引用類型,則不能比較(T a,b;判斷 a==b)
- 不能將操作符(+、-、*、/)用于泛型變量(不能編譯)