程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> CLR筆記:16.泛型

CLR筆記:16.泛型

編輯:關於.NET

泛型:支持值類型和引用類型,不支持枚舉。

沒有泛型屬性。

泛型的好處:

源代碼保護。使用泛型算法不需要訪問算法的源碼——相對於C++模板

類型安全——相對於ArrayList

更加清晰的源碼——不需要拆箱,顯示轉換

更佳的性能——不用裝箱。測試:循環1000萬次,泛型List<T>與ArrayList分別用時0.1s和 2s

16.1    FCL中的泛型

List<T> 取代ArrayList

Directory<TKey, TValue>取代HashTable

Stack<T>,Queue<T>分別取代Stack,Queue

IList,IDirectory,ICollection,IEnumerator,IEnumerable,IComparer,IComparable分別由相 應的泛型接口(加上<T>)

16.3    泛型基礎結構

這一節的前言很有意思:如何在已有的CLR中添加泛型:

創建新的IL泛型指令

修改元數據格式,以支持泛型參數/類型/方法

修改各種語言C#/VB.NET

修改編譯器csc,使之生成新的IL泛型指令/元數據

修改JITer,使之可以處理新的IL泛型指令

創建新的反射成員:泛型參數/類型/方法

修改調試器

修改vs2005智能感知

CLR為應用程序使用的每個類型創建一個內部數據結構,稱為“類型對象”。

具有泛型類型參數的一個類型class<T>,仍然是一個類型,也具有一個類型對象,稱為“開放 式類型”,CLR禁止構造開放式類型的實例;

當T是一個實際類型時class<Guid>,稱為“封閉式類型”,CLR中可以創建封閉式類型的實例。

語法Activator.Creator(type):根據type獲取其一個實例。有

typeof(Dictionary< , >)    //運行期會報錯,使用了“開放式類型”

t = typeof(Dictionary<String , String>)   

Console.WriteLine(t.ToString());    //輸出  System.Collections.Generic.Dictionary`2 [System.String,System.String],這就是泛型Dictionary<String , String>的類型,在IL中也生 成諸如Dictionary`2的方法,這裡,2表示類型參數的數量。

每個封閉式類型都有自己的靜態字段,也就是說,List<String>和List<Int32>具有各自 的靜態字段和cctor。

在泛型上定義cctor的目的:為類型參數加限制條件。例如,希望泛型只用於處理enum——解決了無法 用約束控制Enum:

    internal class GenericOnlyForEnum<T>

    {

        static GenericOnlyForEnum()

        {

            if (!typeof(T).IsEnum)

            {

                throw new Exception();

            }

        }

    }

泛型類型與繼承

由於List<T>從System.Object派生,所以List<String>也是從System.Object派生。

書中演示了一個例子——定義並使用一個鏈表節點類,很值得琢磨,原理就是利用上面這句話。

方法1:

    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 static class Program

    {

        static void Main()

        {

            Node<Char> head = new Node<char>('C');

            head = new Node<char>('B', head);

            head = new Node<char>('A', head);

        }

    }

評論:這個方法不靈活,只能構造同一種泛型Node<char>。

其實呢,是因為Node<T>這個類定義的太大了,可以一拆為二:Node類和TypeNode<T>派 生類,其中Node類只記錄next節點。而在其子類TypeNode<T>中,設置具體數據,如下方法2:

class Node

    {

        protected Node m_next;


        public Node(Node next)

        {

            m_next = next;

        }

    }


    class TypeNode<T> : Node

    {

        public T m_data;


        public TypeNode(T data) : this(data, null) { }


        public TypeNode(T data, Node next)

            : base(next)

        {

            m_data = data;

        }

    }

這時候,就可以任意設置數據了,因為這次head聲明為Node基類型,所以可以改變其子類:

        static void Main()

        {

            Node head = new TypeNode<Char>('A');

            head = new TypeNode<DateTime>(DateTime.Now, head);

        }

泛型類型同一性:

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

接下來就可以使用XXX來代替List<System.DateTime>

——這只是對封閉性類型而言的,一種簡寫

代碼爆炸:

對於泛型,CLR為每種不同的方法/類型組合生成本地代碼,乘法原理——Assembly越來越大

由此,CLR有專門優化措施:

對於引用類型的類型實參,只編譯一次,以後可以共享代碼——因為都是指針,可以看作相同類型; 對於值類型,不能共享,還是要“爆炸”的。

16.4 泛型接口

以FCL中的IEnumerator<T>為例:

    public interface IEnumerator<T> : IDisposable, IEnumerable

    {

        T Current { get;}

    }

有兩種實現方式:

    //1.指定T

    class A : IEnumerable<String>

    {

        private String current;


        public String Current { get { return current; } }

    }


    //2.不指定T

    class B : IEnumerable<T>

    {

        private T current;


        public T Current { get { return current; } }

    }

16.5 泛型委托

泛型委托允許值類型實例傳遞給回調方法時不裝箱。

16.6 泛型方法

如果泛型類中有泛型方法,則各自聲明的類型參數不能重名。

泛型類的ctor等於同名非泛型類的ctor

使用泛型方法和ref進行Swap操作

類型推導:自動判斷泛型方法要使用的類型,如下:

        static void Swap<T>(ref T o1, ref T o2) { }

可以直接放入兩個Int32:

            Int32 n1 = 1, n2 = 2;

            Swap(ref n1, ref n2);

但是,以下使用會在編譯期報錯,即使是實際引用相同的類型,但是推導是根據聲明類型來判斷的:

            String s1 = "s1";

            Object s2 = "s2";

            Swap(ref s1, ref s2);

泛型方法重載:

        static void Display(String s)

        {

            Console.WriteLine(s);

        }


        static void Display<T>(T o)

        {

            Display(o.ToString());

        }

這時,調用方法就有學問了:

            Display("s1");

編譯器總是優先選擇更顯示的匹配,於是以上語句直接調用非泛型方法;否則會陷入死循環。

16.7 泛型和其他成員

C#中,屬性/索引/事件/操作符方法/ctor/finalzer不能有泛型方式。

允許有的:class/struct/interface/funcction/delegate

16.8 可驗證性和限制

在泛型中,類型參數T,只能使用System.Object的方法,比如說ToString();不能使用其他任何方法 ,因為不知道T類型是否支持該方法。

使用where約束,從而可以使用T的更多方法:

where T:Icomparable<T>    告訴編譯器,T表示的類型,必須實現相應的 Icomparable<T>接口,從而T類型實例可以使用該接口的CompareTo方法。

where可以用於class和function

泛型方法重載原則:只能基於類型參數的個數進行重載,個數為0表示非泛型方法。

對於class,可以有:

    class AType { }

    class AType<T> { }

    class AType<T1, T2> { }


    //class AType<T1, T2> where T : IComparable<T> { }

被注釋的類不可以再聲明——class也是根據類型參數的個數,但是不考慮where約束的有無

對於泛型虛方法,重寫的版本必須指定相同數量的參數類型,會繼承基類方法的約束,但是不可以再 指定任何新的約束。

3種約束:

1.主要約束:class和struct,分別約束類型參數為引用類型和值類型

    class ForStruct<T> where T : struct

    {

        public T Test()

        {

            return new T();     //只有值類型可以new T();

        }

    }
    class ForClass<T> where T : class

    {

        public void Test()

        {

            T temp = null;      //只有引用類型可以使用null

        }

    }

對於T:class約束,T可以是類/接口/委托/數組類型

2.次要約束:接口約束和裸類型約束

接口約束見14章筆記,這裡主要講裸類型約束。

裸類型約束,在類型參數間存在一個關系where T1:T2,這裡可以寫為where String:String,即兼 容於自身

3.構造器約束:類型參數一定實現了public的無參構造器:where  T:new()

不能同時指定構造器約束和struct約束——這是多余的,因為所有值類型都隱式實現public無參構造 器

上面提到:只有值類型可以new T(); 但是,如果有了new()約束,引用類型也可以使用new T()了,因 為肯定有構造器,所以new的動作總是可以執行。示例如下:

 public class ConstructConstraint<T> where T : new()

    {

        public static T Factory()

        {

            return new T();

        }

    }
    public static class Program

    {

        static void Main()

        {

            ConstructConstraint<A>.Factory();
        }

    }

其他可驗證問題:

1.不能顯示將泛型類型變量轉型,要先轉為Object,再顯示轉換;對於值類型,要使用as操作符來控 制運行期錯誤

        static void CastingGeneric<T>(T obj)

        {

            //Int32 x = (Int32)obj;     不能這麼寫
            //值類型,應該這麼寫

            Int32 x = (Int32)(Object)obj;


            //引用類型,最好這麼寫

            String s = obj as String;

        }

2.使用default關鍵字來為泛型變量設置默認值:

T temp = default(T);    //null或者0

3.將泛型變量與null進行比較

對於引用類型,無論是否被約束,都可以使用==和!=比較null

對於值類型,如果未被約束,則永遠為null,因為if(obj==null)永遠為真,這裡並不會報錯;

如果被約束為struct,則不能比較null,因為if(obj==null)結果永遠為真,但是這裡會報錯。

4.兩個泛型類型變量的比較,使用==和!=

引用類型是可以比較的;

兩個值類型不可以使用==比較,除非重載了==操作符。

如果未被約束,則不能比較,除非重載了==操作符,約束為引用可以比較。

5.泛型類型變量作為操作數使用

不能使用操作符(+,-=,*,<等等)操作泛型類型變量

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved