C#核心語法-泛型(詳細講解泛型方法、泛型類、泛型接口、泛型約束,了解協變逆變)


  泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.NET框架引入了類型參數(type parameters)的概念。類型參數使得設計類和方法時,不必確定一個或多個具體參數,其的具體參數可延遲到客戶代碼中聲明、實現。這意味着使用泛型的類型參數T,寫一個類MyList<T>,客戶代碼可以這樣調用:MyList<int>, MyList<string>或 MyList<MyClass>。這避免了運行時類型轉換或裝箱操作的代價和風險。

  • 泛型概述

泛型類和泛型方法兼復用性、類型安全和高效率於一身,是與之對應的非泛型的類和方法所不及。泛型廣泛用於容器(collections)和對容器操作的方法中。.NET框架2.0的類庫提供一個新的命名空間System.Collections.Generic,其中包含了一些新的基於泛型的容器類。要查找新的泛型容器類(collection classes)的示例代碼,請參見基礎類庫中的泛型。當然,你也可以創建自己的泛型類和方法,以提供你自己的泛化的方案和設計模式,這是類型安全且高效的。下面的示例代碼以一個簡單的泛型鏈表類作為示范。(多數情況下,推薦使用由.NET框架類庫提供的List<T>類,而不是創建自己的表。)類型參數T在多處使用,具體類型通常在這些地方來指明表中元素的類型。類型參數T有以下幾種用法:

l        在AddHead方法中,作為方法參數的類型。

l        在公共方法GetNext中,以及嵌套類Node的 Data屬性中作為返回值的類型。

l        在嵌套類中,作為私有成員data的類型。

注意一點,T對嵌套的類Node也是有效的。當用一個具體類來實現MyList<T>時——如MyList<int>——每個出現過的T都要用int代替。

  

using System;
using System.Collections.Generic;
 
public class MyList<T> //type parameter T in angle brackets
    {
        private Node head;
     // The nested type is also generic on T.
        private class Node          
        {
            private Node next;
//T as private member data type:
            private T data;         
//T used in non-generic constructor:
            public Node(T t)        
            {
                next = null;
                data = t;
            }
            public Node Next
            {
                get { return next; }
                set { next = value; }
            }
//T as return type of property:
            public T Data           
            {
                get { return data; }
                set { data = value; }
            }
        }
        public MyList()
        {
            head = null;
        }
//T as method parameter type:
        public void AddHead(T t)    
        {
            Node n = new Node(t);
            n.Next = head;
            head = n;
        }
 
        public IEnumerator<T> GetEnumerator()
        {
            Node current = head;
 
            while (current != null)
            {
                yield return current.Data;
                current = current.Next;
            }
        }
    }

下面的示例代碼演示了客戶代碼如何使用泛型類MyList<T>,來創建一個整數表。通過簡單地改變參數的類型,很容易改寫下面的代碼,以創建字符串或其他自定義類型的表。

class Program
    {
        static void Main(string[] args)
        {
//int is the type argument.
           MyList<int> list = new MyList<int>();
            for (int x = 0; x < 10; x++)
                list.AddHead(x);
 
            foreach (int i in list)
            {
                Console.WriteLine(i);
            }
            Console.WriteLine("Done");
        }
    }
  •  泛型的優點

  針對早期版本的通用語言運行時和C#語言的局限,泛型提供了一個解決方案。以前類型的泛化(generalization)是靠類型與全局基類System.Object的相互轉換來實現。.NET框架基礎類庫的ArrayList容器類,就是這種局限的一個例子。ArrayList是一個很方便的容器類,使用中無需更改就可以存儲任何引用類型或值類型。

//The .NET Framework 1.1 way of creating a list
ArrayList list1 = new ArrayList(); 
list1.Add(3);
list1.Add(105);
//...
ArrayList list2 = new ArrayList();
list2.Add(“It is raining in Redmond.”);
list2.Add("It is snowing in the mountains.");
//...

 

但是這種便利是有代價的,這需要把任何一個加入ArrayList的引用類型或值類型都隱式地向上轉換成System.Object。如果這些元素是值類型,那么當加入到列表中時,它們必須被裝箱;當重新取回它們時,要拆箱。類型轉換和裝箱、拆箱的操作都降低了性能;在必須迭代(iterate)大容器的情況下,裝箱和拆箱的影響可能十分顯著。

 

另一個局限是缺乏編譯時的類型檢查,當一個ArrayList把任何類型都轉換為Object,就無法在編譯時預防客戶代碼類似這樣的操作:

 

ArrayList list = new ArrayList(); 
//Okay.  
list.Add(3); 
//Okay, but did you really want to do this?
list.Add(.“It is raining in Redmond.”);
 
int t = 0;
//This causes an InvalidCastException to be returned.
    foreach(int x in list)
{
  t += x;
}

 

雖然這樣完全合法,並且有時是有意這樣創建一個包含不同類型元素的容器,但是把string和int變量放在一個ArrayList中,幾乎是在制造錯誤,而這個錯誤直到運行的時候才會被發現。

 

在1.0版和1.1版的C#語言中,你只有通過編寫自己的特定類型容器,才能避免.NET框架類庫的容器類中泛化代碼(generalized code)的危險。當然,因為這樣的類無法被其他的數據類型復用,也就失去泛型的優點,你必須為每個需要存儲的類型重寫該類。

 

ArrayList和其他相似的類真正需要的是一種途徑,能讓客戶代碼在實例化之前指定所需的特定數據類型。這樣就不需要向上類型轉換為Object,而且編譯器可以同時進行類型檢查。換句話說,ArrayList需要一個類型參數。這正是泛型所提供的。在System.Collections.Generic命名空間中的泛型List<T>容器里,同樣是把元素加入容器的操作,類似這樣:

 

List<int> list1 = new List<int>();
//No boxing, no casting:
list1.Add(3);
//Compile-time error:
list1.Add("It is raining in Redmond.");

 

與ArrayList相比,在客戶代碼中唯一增加的List<T>語法是聲明和實例化中的類型參數。代碼略微復雜的回報是,你創建的表不僅比ArrayList更安全,而且明顯地更加快速,尤其當表中的元素是值類型的時候。

  • 泛型類型參數

在泛型類型或泛型方法的定義中,類型參數是一個占位符(placeholder),通常為一個大寫字母,如T。在客戶代碼聲明、實例化該類型的變量時,把T替換為客戶代碼所指定的數據類型。泛型類,如泛型概述中給出的MyList<T>類,不能用作as-is,原因在於它不是一個真正的類型,而更像是一個類型的藍圖。要使用MyList<T>,客戶代碼必須在尖括號內指定一個類型參數,來聲明並實例化一個已構造類型(constructed type)。這個特定類的類型參數可以是編譯器識別的任何類型。可以創建任意數量的已構造類型實例,每個使用不同的類型參數,如下:

MyList<MyClass> list1  = new MyList<MyClass>();
MyList<float> list2 = new MyList<float>();
MyList<SomeStruct> list3 = new MyList<SomeStruct>();

在這些MyList<T>的實例中,類中出現的每個T都將在運行的時候被類型參數所取代。依靠這樣的替換,我們僅用定義類的代碼,就創建了三個獨立的類型安全且高效的對象。有關CLR執行替換的詳細信息,請參見運行時中的泛型。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM