詳解C#泛型(一)

Minotauros發表於2018-10-25

  一、C#中的泛型引入了型別引數的概念,類似於C++中的模板,型別引數可以使型別或方法中的一個或多個型別的指定推遲到例項化或呼叫時,使用泛型可以更大程度的重用程式碼、保護型別安全性並提高效能;可以建立自定義的泛型型別(類、結構、介面、委託)和泛型方法;

  1.在泛型型別的定義或泛型方法的宣告中,型別引數是型別的佔位符,這些佔位符指代的型別需要在例項化泛型型別或呼叫泛型方法時進行指定;

  ※型別引數一般以T命名,如果是多個,使用T、U、V等,如果有指定約束,可以結合約束命名,例如需要繼承自MyClass的型別引數命名為TMyClass;任何命名都並不會給型別引數增加額外作用;

  2.泛型是執行時起作用的一套機制,根據執行時型別引數被指定為值型別還是引用型別其使用方式有所不同:

  ※當型別引數被指定為值型別時,會在第一次指定該特定值型別的型別時建立該型別唯一的專用化泛型型別,泛型型別中的型別引數會被替換為相應的值型別;

  ※當型別引數被指定為引用型別時,會在第一次指定任意引用型別時建立一個通用化泛型型別,泛型型別中的型別引數會被替換為該引用型別,並在之後每次指定為引用型別時重用該泛型型別並修改其中型別引數的型別;造成這種差異的原因可能在於所有的引用大小相同;

  二、這篇我們先了解下泛型類,泛型類的定義中可以將型別引數用作成員變數的型別或方法中引數列表、返回值的型別:

class MyClass<T> //宣告具有一個型別引數的泛型類,可以有多個型別引數,用,隔開:<T, U>
{
    public T MyObj; //宣告T型別的欄位
    private Type myGenericField = typeof(T); //在泛型類內部可以獲取型別引數的型別資訊
    //do…
}
//宣告泛型類的例項,指定型別引數為int型別
MyClass<int> myObj = new MyClass<int>();

  1.在定義泛型型別時,可以對型別引數的種類新增限制,這些限制稱為約束(Constraint),使用約束可以增加型別引數所能進行操作和呼叫方法的數量;約束使用上下文關鍵字where指定,位於基類和介面之後,例如:

class MyClass<T> : MyBaseClass where T : MyType //指定基類約束,T需要是指定的類MyType或繼承自類MyType,基類約束需要在所有約束之前,基類約束本身也可以是泛型型別,例如MyType<T>

  ※其它特殊的約束:

  where T : IMyInterface //指定介面約束,T需要是指定的介面IMyInterface或實現介面IMyInterface,可以同時指定多個介面約束,介面約束本身也可以是泛型型別,例如IMyInterface<T>
  where T : class //指定型別約束,T需要是類型別
  where T : struct //指定型別約束,T需要是值型別,但不可以是可空型別
  where T : new() //型別引數必須有公共的無引數建構函式,與其他約束一起使用時,new()約束必須最後指定;由於結構的定義中一定包含無引數建構函式,所以struct約束包含new()約束,二者不可同時使用,通常與class約束一起使用:class, new()
  where T : struct where U : class //給多個型別引數指定約束
  where U : T //型別引數作為約束,型別引數U繼承自型別引數T

  ※可以對一個型別引數應用多個約束,多個約束使用,隔開:where T : MyType, IMyInterface;

  ※沒有約束的型別引數稱為未繫結型別引數(Unbounded Type Parameter),這些型別引數的變數在使用時不可以使用==和!=運算子,因為無法保證執行時指定的型別支援這些運算子;

  ※對於使用型別約束class的型別引數,應避免對其變數使用==和!=運算子,因為在泛型中這些運算子僅會根據引用來判斷是否相等,即使型別對==和!=運算子進行了過載也不行;如果必須根據值進行判斷,應給型別引數加入基類約束IEquatable<T>或IComparable<T>並在型別中實現它們,比較時使用Equals或CompareTo;

  ※從C#7.3開始,可以使用特殊型別System.Delegate、System.MulticastDelegate和System.Enum作為基類約束中的基類,還可以使用非託管型別unmanaged作為型別約束中的型別;

  2.非泛型類(即具體類,Concrete Class)只可以繼承自具體類或封閉式構造類(即指定了所有型別引數的泛型類,Closed Constructed Class),不可以繼承自開放式構造類(即沒有完全指定所有型別引數的泛型類,Open Constructed Class);泛型類可以繼承自具體類和封閉式構造類,也可以繼承自開放式構造類,繼承自開放式構造類時,派生類的型別引數中必須包含基類中未指定型別的型別引數,同時,派生類中這些型別引數的約束必須為基類中對應型別引數約束的超集;可以總結為以下幾種情況:

//對於基類為具體類或僅有一個型別引數的情況
class BaseClass { }
class BaseGenericClass<T> { }
//定義一個泛型類,繼承具體類BaseClass
class MyClass<T> : BaseClass { }
//定義一個泛型類,繼承自指封閉式構造類BaseGenericClass<int>
class MyClass<T> : BaseGenericClass<int> { }
//定義一個泛型類,繼承自開放式構造類BaseGenericClass<T> 
class MyClass<T> : BaseGenericClass<T> { }
//定義一個非泛型類,繼承自封閉式構造類BaseGenericClass<int>
class MyClass : BaseGenericClass<int> { }
//對於基類有多個型別引數的情況
class MultipleBaseGenericClass<T, U> { }
//定義一個泛型類,繼承自開放式構造類MultipleBaseGenericClass<T, int>
class MyClass<T> : MultipleBaseGenericClass<T, int> { }
//定義一個泛型類,繼承自開放式構造類MultipleBaseGenericClass<T, U>
class MyClass<T, U> : MultipleBaseGenericClass<T, U> { }
//定義一個非泛型類,繼承自封閉式構造類MultipleBaseGenericClass<int, string>
class MyClass : MultipleBaseGenericClass<int, string> { }

  ※在繼承中,派生類型別的物件可以通過隱式轉換賦值給基類型別的變數,這同樣適用於泛型型別的物件,例如泛型類List<T>繼承自泛型介面IList<T>,那麼可以把List<int>型別的物件隱式轉換為IList<int>型別的變數:

IList<int> iList = new List<int>();

  3.泛型類為不可變數,泛型型別相同但型別引數指定型別不同的泛型型別即是不同的型別,它們之間不能進行型別轉換(即使型別引數指定的型別之間存在型別轉換關係),例如不能把List<DerivedClass>型別的物件賦值給一個List<BaseClass>型別的變數;

  4.泛型類最常見的用途是建立泛型集合類,在名稱空間System.Collections.Generic中包含系統定義的各種泛型集合類,應儘可能的使用這些泛型集合來代替普通的集合;

 


如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的認可是我寫作的最大動力!

作者:Minotauros
出處:https://www.cnblogs.com/minotauros/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

相關文章