泛型

程序= 算法 + 數據。編程人員設計好一種算法,例如排序、比較、交換等等。這些算法應該應用於不一樣的數據類型,而不是爲每一個數據類型都寫一個專有的算法。算法

CLR 容許建立:泛型引用類型,泛型值類型,泛型接口,泛型委託。編程

CLR 不容許建立:泛型枚舉類型。數組

在FCL 中泛型最明顯的應用就是集合類,FLC 在 System.Collections.Generic 和 System.Collections.ObjectModel 命名空間中提供了多個泛型集合類。System.Collections.Concurrent 命名空間則提供了線程安全的泛型集合類。安全

 

開放類型和封閉類型less

具備泛型類型參數的類型任然是類型,CLR 一樣會爲它建立內部的類型對象。這一點適合引用類型(類)、值類型(結構)、接口類型和委託類型。具備泛型類型的參數類型稱爲開放類型,CLR 禁止構造開放類型的任何實例。這相似於CLR 禁止構造接口類型的實例。性能

代碼引用泛型類型時可指定一組泛型類型實參。爲全部類型參數都傳遞了實際的數據類型,類型就成了封閉類型。CLR容許構造封閉類型的實例。ui

using System;
using System.Collections.Generic;
// A partially specified open type
internal sealed class DictionaryStringKey<TValue> :
    Dictionary<String, TValue> {
}
public static class Program {
    public static void Main() {
            Object o = null;
            // Dictionary<,> is an open type having 2 type parameters
            Type t = typeof(Dictionary<,>);
            // Try to create an instance of this type (fails)
            o = CreateInstance(t);
            Console.WriteLine();
            // DictionaryStringKey<> is an open type having 1 type parameter
            t = typeof(DictionaryStringKey<>);
            // Try to create an instance of this type (fails)
            o = CreateInstance(t);
            Console.WriteLine();
            // DictionaryStringKey<Guid> is a closed type
            t = typeof(DictionaryStringKey<Guid>);
            // Try to create an instance of this type (succeeds)
            o = CreateInstance(t);
            // Prove it actually worked
            Console.WriteLine("Object type=" + o.GetType());
        }
        private static Object CreateInstance(Type t) {CHAPTER 12 Generics 273
        Object o = null;
        try {
            o = Activator.CreateInstance(t);
            Console.Write("Created instance of {0}", t.ToString());
        }
        catch (ArgumentException e) {
            Console.WriteLine(e.Message);
        }
        return o;
    }
}

 

編譯並容許上述代碼獲得下面的結果:this

Cannot create an instance of System.Collections.Generic.
Dictionary`2[TKey,TValue] because Type.ContainsGenericParameters is true.
Cannot create an instance of DictionaryStringKey`1[TValue] because
Type.ContainsGenericParameters is true.
Created instance of DictionaryStringKey`1[System.Guid]
Object type=DictionaryStringKey`1[System.Guid]

 

泛型類型和繼承atom

泛型類型任然是類型,因此能從其餘任何類型派生。使用泛型類型並指定類型參數時,實際是在CLR 中定義了一個新的類型對象,這個新的類型對象從泛型類型派生自的哪一個類型派生。例如: List<T> 從 Object 派生,因此List<String> 和 List<Giud> 也從Object 派生。指定類型實參不影響繼承層次結構,理解這一點,有助於你判斷哪些強制類型轉換是容許的,哪些不容許。spa

 

泛型類型同一性

同一性就是爲了方便使用泛型類型,能夠像C++中的宏定義那樣將一個泛型類型用其餘的符號表明。C# 容許使用簡化的語法來引用泛型封閉類型,同時不會影響類型的相等性。這個語法要求在源文件頂部使用傳統的using 指令,例如:

using DateTimeList = System.Collections.Generic.List<System.DateTime>;

執行下面的代碼驗證一下:

Boolean sameType = (typeof(List<DateTime>) == typeof(DateTimeList));

 

代碼爆炸

使用泛型類型參數的方法在進行JIT 編譯時,CLR 獲取方法的IL,用指定的類型實參替換,而後建立恰當的本機代碼(這些代碼是爲操做指定數據類型「量身定製的」),這正是泛型的重要特色。但這樣作也有一個缺點: CLR 要爲每種不一樣的方法/類型 組合生成本機代碼。咱們將這個現象稱爲代碼爆炸。它可能會形成應用程序的工做集顯著增大,從而損害性能。

CLR 內建了一些措施能緩解代碼爆炸。

一、一次編譯,重複使用。爲特定的類型實參調用了一個方法後,之後再調用相同的類型實參的方法時,CLR只會在第一次編譯代碼。

二、CLR 認爲全部引用類型實參都徹底相同,因此代碼可以共享。對於任何引用類型的實參,都會調相同的代碼。

可是假如某個類型實參是值類型,CLR 就必須專門爲哪一個值類型生成本機代碼。

 

泛型接口

若是沒有泛型接口,每次用非泛型接口(如 IComparable)來操縱值類型都會發生裝箱,並且會失去編譯時的類型安全性。所以,CLR 提供了對泛型接口的支持。引用類型或值類型可指定類型實參實現泛型接口。也可保持類型實參的未指定狀態來實現泛型接口。

 

  

泛型委託

CLR 支持泛型委託,目的是保證任何類型的對象都能以類型安全的方式傳給回調方法。泛型委託容許值類型實例在傳給回調方法時不進行任何裝箱。

 

委託和接口的逆變和協變泛型類型實參

委託的每一個泛型類類型參數均可以標記爲協變量或逆變量。利用這個功能,可將泛型委託類型的變量轉換爲相同的委託類型(但泛型參數類型不一樣)。泛型類型參數能夠是如下任何一種形式。

  • 不變量,意味着泛型類型參數不能改變。

  • 逆變量,意味着泛型類型參數能夠從一個類更改成它的某個派生類。C# 用in 關鍵字標識逆變量。

  • 協變量,意味着泛型類型參數能夠從一個類更改成它的某個基類。C# 用 out 關鍵字標識協變量。

 

泛型和其餘成員

在C# 中,屬性、索引器、事件、操做符方法、構造器和終結器自己不能有類型參數。但它們能在泛型類型中定義,並且這些成員中的代碼能使用類型的類型參數。

 

可驗證型和約束

約束的做用是限制能指定成泛型實參的類型數量。經過限制類型的數量,能夠對哪些類型執行更多的操做。

編譯器/CLR 容許向類型參數應用各類約束。能夠用一個主要約束、一個次要約束以及/或者一個構造器約束來約束類型參數。

主要約束

主要約束能夠是表明非密封類的一個引用類型。不能指定如下特殊引用類型:SystemObject,SystemArray,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum 或者 System.Void。

指定引用類型約束時,至關於向編譯器承諾:一個指定的類型實參要麼是與約束類型相同的類型,要麼是從約束類型派生的類型。例如如下泛型類:

internal sealed class PrimaryConstraintOfStream<T> where T : Stream {
    public void M(T stream) {
        stream.Close();// OK
    }
}

 

有兩個特殊的主要約束:class 和 struct。其中,class 約束向編譯器承諾類型實參是引用類型。任何類類型、接口類型、委託類型或者數組類型都知足這個約束。

struct 約束向編譯器承諾類型實參是值類型。包括枚舉在內的任何值類型都知足這個約束。可是編譯器和 CLR 將任何System.Nullable<T> 值類型視爲特殊類型,不知足這個 struct 約束。

 

次要約束

次要約束表明接口類型。這種約束向編譯器承諾類型實參實現了接口。這種約束向編譯器承諾類型實參實現了接口。因爲能指定多個接口約束,因此類型實參必須實現了全部接口約束。

還有一種次要約束稱爲 類型參數約束,有時也稱爲 裸類型約束。它容許一個泛型類型或方法規定:指定的類型實參要麼就是約束的類型,要麼就是約束的類型的派生類。一個類型參數能夠指定零個或者多個類型參數約束。下面這個泛型方法演示瞭如何使用類型參數約束:

private static List<TBase> ConvertIList<T, TBase>(IList<T> list) where T : TBase {
    List<TBase> baseList = new List<TBase>(list.Count);
    for (Int32 index = 0; index < list.Count; index++) {
        baseList.Add(list[index]);
    }
    return baseList;
}

ConvertList 方法指定了兩個類型參數,其中T 參數由Tbase 類型參數約束。意味着無論爲T 指定什麼類型實參,都必須兼容於爲 TBase 指定的類型實參。

 

構造器約束

類型參數可指定零個或一個構造器約束,它向編譯器承諾類型實參是實現了公共無參構造器的非抽象類型。如下示例類使用構造器約束來約束它的類型參數:

internal sealed class ConstructorConstraint<T> where T : new() {
    public static T Factory() {
        // Allowed because all value types implicitly
        // have a public, parameterless constructor and because
        // the constraint requires that any specified reference
        // type also have a public, parameterless constructor
        return new T();
    }
}

 

在上述例子中 new T() 是合法的,由於已知 T 是擁有公共無參構造器的類型。對全部值類型來講,這一點(擁有公共無參構造器)確定成立。對於做爲類型實參指定的任何引用類型,這一點也成立,由於構造器約束要求它必須成立。

 

其餘可驗證性問題

一、將泛型類型的變量轉型爲其餘類型是非法的,除非轉型爲與約束兼容的類型。

private static void CastingAGenericTypeVariable1<T>(T obj) {
    Int32 x = (Int32) obj; // Error
    String s = (String) obj; // Error
}
private static void CastingAGenericTypeVariable2<T>(T obj) {
    Int32 x = (Int32) (Object) obj; // No error
    String s = (String) (Object) obj; // No error
}

二、將泛型類型變量設爲 null 是非法的,除非將泛型類型約束成引用類型。

三、不管泛型類型是否被約束,使用== 或 != 操做符將泛型類型變量與 null 進行比較都是合法的。若是T 被約束成 struct , C# 編譯器會報錯。值類型的變量不能與null 進行比較,由於結果始終同樣。

四、若是泛型類型參數不能確定是引用類型,對同一個泛型類型的兩個變量進行比較是非法的:

private static void ComparingTwoGenericTypeVariables<T>(T o1, T o2) {
    if (o1 == o2) { } // Error
}

五、將操做符用於泛型類型的操做會引起大量問題。C# 知道如何解釋應用於基元類型的操做符(好比+,- ,* 和/)。可是不能將這些操做符用於泛型類型的變量。編譯器在編譯時肯定不了類型。

相關文章
相關標籤/搜索