1. 程式人生 > 實用技巧 >CF1407E-Egor in the Republic of Dagestan【思維+最短路】

CF1407E-Egor in the Republic of Dagestan【思維+最短路】

1. 泛型的意義

型別安全(更好的編譯時檢查),效能增強(避免裝箱和拆箱),程式碼複用(減少了功能相似的程式碼)

2. 種類:

  • 泛型型別(包括類,結構,介面,委託,陣列)
  • 泛型方法。

    列舉、屬性、建構函式、欄位 這些沒有對應的泛型

3. 約束 :

  • where T: class (必須是引用型別)
  • where T:struct (必須是值型別,nullable 型別的值型別除外)
  • where T: 基類名 (限制類型引數必須是繼承自指定的基類,或是基類本身)
  • new() (必須有無參建構函式,只能用於引用型別,並且必須放在約束的最後如 class Sample<T> where T : class, IDisposable, new()
    )
  • where T:介面名 (限制類型引數必須是實現指定介面)
  • where T:U (為 T 提供的型別引數必須是為 U 提供的引數或派生自為 U 提供的引數。這稱為裸型別約束.)
  • 組合約束

    上面的幾種約束可以形成組合約束。若一個型別引數有多個約束,約束之間用 , 分開; 如果包含多個型別引數 ,多個型別引數用 where 分開。如

    public class KeyValue<TKey,TValue> where TKey : new() where TValue: Base,new()

4. 相關術語:

  • 形參(一般用 paramter 描述)
  • 實參(一般用 argument 描述)
  • 型別引數:是一種形參,如 List<T> 中的 T
  • 型別實參 ,如 List<int> 中的 int
  • 泛型的 開放型別 指的是帶有 型別引數(且未分配型別實參) 的型別,如 Type myType= typeof(List<T>) ;中的變數 myType;
  • 泛型的 封閉型別; 已經分配具體 型別實參 的型別,如 Type myType2= typeof(List<int>) ; 中的變數 myType2;

5. 泛型方法

  • 帶有型別引數的方法不一定都是泛型方法
 public class Animal<T>
    {
        public void Move1(T speed) { }

        public void Move2<S>(S speedT) { }
    }

Move2 是泛型方法 , Move1 則不是. 判斷一個方法是否是泛型方法,可以通過反射獲取到 MethodInfo 物件,然後呼叫 物件的 IsGenericMethod 方法.

  • 方法過載: 呼叫方法時,如果有多個方法簽名相匹配,則優先呼叫型別引數較少的方法

  • 方法呼叫時 型別引數的省略,如果是下面的 2 種情況則可以省略型別引數

    • 使用型別明確的值,傳給被呼叫的方法時 如
        Move<int>(100); // 可以簡寫成  Move(100);
    
    • 使用泛型方法呼叫另一個具有相同方法簽名的泛型方法時
    public void Move<T>(T speed){}

    public void MoveWrapper<T>(T s)
    {
        Move<T>(s);// 可以簡寫成  Move(s);
    }

4. 協變和逆變:

  • 協變和逆變指的是泛型的型別實參之間的型別轉換 。且協變和逆變都是型別安全的.

C# 只對 介面委託 提供可變性支援(包括協變和逆變),另外支援 陣列 的協變(但是僅僅是編譯時不報錯,允許時訪問到內部的具體元素還是會出現執行時異常,這樣設計的原因據說是為了相容 Java)。 型別引數前加 inout ,這種寫法是 C# 4.0 才引人的, 這也表明之前的 C#版本不支援協變和逆變

  • 協變(Covariance 允許使用比原始指定的型別派生程度更大的型別)

    通用的表示是 Ixxx<父類或父介面> iBase = new xxx<子類或子介面>(); ,

    其中 Ixxx介面中必然有 型別引數 前面帶有 out 關鍵字修飾 如: IEnumerable<Animal> animals= new List<Cat>(); 當泛型的型別引數只描述返回型別引數(輸出)的操作時,協變是型別安全的; 若型別引數前加 out ,表明該型別只能作為函式的返回型別使用,不能作為輸入

  • 逆變(Contravariance 允許使用比原始指定的型別派生程度更小的型別)

    通用的表示是 Ixxx<子類或子介面> iBase = new xxx<父類或父介面>(); 其中 Ixxx介面中必然有 型別引數 前面帶有 in 關鍵字修飾。 如:

    Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
    Action<Derived> d = b;
    d(new Derived());

當型別引數只描述接受型別引數(輸入)的操作時,逆變就是安全的 。 若型別引數前加 in ,表明該型別只能作為函式的輸入型別使用,不能作為輸出型別。

  • 不變體(Invariance 只能使用原始指定的型別;泛型型別引數既不是協變型別,也不是逆變型別) 如: List<Cat> cats= new List<Cat>();

5. 進階內容

  • 泛型型別內的靜態欄位:

    不同的封閉型別具有不同的靜態欄位

  • JIT 編譯器如何處理泛型
    1. JIT 為每個以值型別作為型別實參的 封閉型別 都建立不同的程式碼
    2. 所有使用引用型別作為型別實參的 封閉型別 都共享相同的本機程式碼 這是因為所有引用型別的變數實際上只是指向堆上物件的指標.而所有的指標都可以使用相同的方式操作.