1. 程式人生 > >CLR via C#學習筆記-第十二章-泛型

CLR via C#學習筆記-第十二章-泛型

vat message 靜態 定義 泛型接口 void 失敗 internal 運行時

泛型是CLR和編程語言提供的一種特殊機制,他支持另一種形式的代碼重用,即算法重用。

CLR允許創建泛型引用類型和泛型值類型,但不允許創建泛型枚舉類型。

此外CLR還允許創建泛型接口和泛型委托。方法偶爾也封裝有用的算法,所以CLR允許在引用類型、值類型或接口中定義泛型方法。

定義泛型方法或類時,為類型指定的任何變量比如T都稱為類型參數type parameter。

使用泛型類型或方法是指定的具體數據類型為類型實參type argument。

12.1 FCL中的泛型

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

System.Collections.Concurrent命名空間則提供了安全的泛型集合類。微軟建議使用泛型集合類,不建議使用非泛型集合類。

因為非泛型集合類沒有類型安全性、更清晰地代碼和更佳的性能;泛型類有更好的對象模型,如虛方法的數量顯著減少,性能更好。

12.2 泛型基礎結構

12.2.1 開放類型和封閉類型

具有泛型類型參數的類型仍然是類型,CLR同樣會為他創建內部的類型對象。

然而具有泛型類型參數的類型稱為開放類型,CLR禁制構造開放類型的任何實例。類似於CLR禁止構造接口類型的實例。

代碼引用泛型類時可指定一組泛型類型實參。為所有類型參數都傳遞了實際的數據類型,類型就成為封閉類型。

CLR允許構造封閉類型的實例。然而代碼引用泛型類型的時候,可能留下一些泛型類型實參未指定。

這會在CLR中創建新的開放類型對象,而且不能創建該類型的實例:

internal sealed class DictionaryStringKey<Tvalue> : Dictionary<String, Tvalue> { }
public static class Program{
    public static void Main(string[] args){
        Object o = null;
        //1.Dictionary<,>是開放類型
Type t = typeof(Dictionary<,>); //創建實例,失敗 o = CreateInstance(t); //2.DictionaryStringKey<>是開放類型,有一個類型參數 t = typeof(DictionaryStringKey<>); //創建實例,失敗 o = CreateInstance(t); //3.DictionaryStringKey<Guid>是封閉類型 t = typeof(DictionaryStringKey<Guid>); //創建實例,成功 o = CreateInstance(t); } private static Object CreateInstance(Type t){ Object o = null; try{ o = Activator.CreateInstance(t); Console.WriteLine($"已創建{t.ToString()}的實例"); } catch(ArgumentException e){ Console.WriteLine(e.Message); } return o; } }

封閉類型靜態構造器的作用

每個封閉類型都有自己的靜態字段。換言之,假如List<T>定義了任何靜態字段,這些字段不會在一個List<DateTime>或List<String>之間共享。

每個封閉類型對象都有自己的每個封閉類型,這樣構造器都會執行一次。

泛型類型定義靜態構造器的目的是保證傳遞的類型實參滿足特定條件。

例如我們可以像下面這樣定義只能處理枚舉類型的泛型類型:

internal sealed class GenericTypeThatRequiresAnEnum{
    static GenericTypeThatRequiresAnEnum(){
        if(!typeof(T).IsEnum){
            throw new ArgumentException("T must be an enumerated type");
        }
    }
}

CLR提供了名為約束的功能,可以更好地制定有效的類型實參。遺憾的是約束無法將類型實參限制為僅枚舉類型。

所以上例需要用靜態構造器來保證類型是一個枚舉類型。

12.2.2 泛型類型和繼承

泛型類型仍然是類型,所以能從其他任何類型派生。

使用泛型類型並指定類型實參時,實際是在CLR中定義一個新的類型對象,新的類型對象從泛型類型派生自的那個類型派生。

換言之,由於List<T>從Object派生,所有List<String>等也稱Object派生。

指定類型實參不影響繼承層次結構。

假定下面這樣定義一個鏈表節點類:

internal sealed class Node<T>{
    public T m_data;
  public Node<T> m_next;
    public Node(T data):this(data,null){}
    public Node(T data,Node<T> next){
        m_data=data;
        m_next=next;
    }
    public override String ToString(){
        return m_data.ToString()+((m_next!=null)?m_next.ToString():String.Empty);
    }
}

那麽可以寫代碼來構造鏈表:

private static void SameDataLinkedList(){
    Node<Char> head=new Node<Char>(C);
    head=new Node<Char>(B,head);
    head=new Node<char>(A,head);
    Console.WriteLine(head.ToString);//顯示"ABC"
}

泛型類繼承非泛型基類

在上面這個Node類中,對於m_next字段引用的另一個節點來說,其m_data字段必須包含相同的數據類型。

所以更好的辦法是定義非泛型Node基類,再定義非泛型TypedNode類繼承Node基類。

這樣就可以創建一個鏈表,其中每個結點都可以是一種具體的數據類型,除了不能是Object。

同時獲得編譯時的類型安全性,並防止值類型裝箱。下面是新的類型定義:

internal class Node{
    public Node<T> m_next;
    public Node(Node<T> next){
        m_next=next;
    }
}
internal sealed class TypedNode<T>:Node{
    public T m_data;
    public Node(T data):this(data,null){}
    public Node(T data,Node<T> next){
        m_data=data;
    }
    public override String ToString(){
        return m_data.ToString()+((m_next!=null)?m_next.ToString():String.Empty);
    }
}

現在可以寫代碼創建一個鏈表,其中每個結點都是不同的數據類型。

private static void DifferentDataLinkedList(){
    Node head=new TypedNode<char>(.);
    head=new TypedNode<DateTime>(DateTime.Now,head);
    head=new TypedNode<String>("Today is",head);
    Console.WriteLine(head.ToString());
}

12.2.3 泛型類型同一性

錯誤的簡化

為了簡化下面這樣的代碼:

List<DataTime> dt1=new Lis<DateTime>();

一些開發人員可能首先定義下面這樣的類:

internal sealed class DataTimeList:List<DateTime>{/*無需放入任何代碼*/}

然後就可以簡化創建列表的代碼了:

DateTimeList dt1=new DateTimeList();

這樣便面試方便了,但是絕對不要單純出於增強源碼可讀性的目的來定義一個新類。

這樣會喪失同一性identity和相等性equivalence,如下所示:

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

上述代碼運行時,sameType會被初始化為false,因為比較的是兩個不同的類型的對象。

也意味著如果方法的原型接受一個DateTimeList就不可以將一個DList<DateTime>傳給它。

然而如果方法的原型接受一個List<DateTime>,可以將一個DateTimeList傳給他,因為後者從前者派生。使人糊塗。


使用using簡化語法

C#允許使用簡化的語法來引用泛型封閉類型,同時不會影響類型的相等性,其要求在源文件頂部使用傳統using指令:

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

using指令實際定義的是名為DateTimeList的符號。

編譯時會將所有DateTimeList替換成System.Collection.Generic.List<System.DateTime>。

這樣類型的同一性和相等性得到了維持。

此外可以利用C#的隱式類型局部變量功能,讓便一起根據表達式的類型來推斷方法的局部變量的類型。

CLR via C#學習筆記-第十二章-泛型