泛型簡介:
泛型(Generic Type)是.NET Framework2.0最強大的功能之一。泛型的主要思想是將算法與數據結構完全分離開,使得一次定義的算法能作用於多種數據結構,從而實現高度可重用的開發。通過泛型可以定義類型安全的數據結構,而沒有必要使用實際的數據類型,這將顯著提高系統性能並得到高質量的代碼(因為可以重用數據處理算法,沒有必要復制類型特定的代碼)。
泛型工作原理:
通過泛型可以定義類型安全並且對性能或工作效率無損害的類。表面上,C#泛型的語法和C++模板類似,但編譯器在實現和支持他們的方式存在重要的差異。與C++模板相比,C#泛型可以提供增強的安全性,但在功能方面也受到某種程度的限制。在一些C++編譯器中,在通過特定類型使用模板類之前,編譯器甚至不會編譯模板代碼,當確實指定了類型時,編譯器會以內聯方式插入代碼,並且將每個出現一般類型參數的地方替換為指定的類型。此外,每當使用特定類型時,編譯器都會插入特定於該類型的代碼,而不管是否已經在應用程序的其他位置為模板類指定了該類型,其中C++鏈接器負者解決該問題,但並不總是有效,而這也可能會導致代碼膨脹,從而增加加載時間和內存足跡。
在.net Framewrok 2.0 中,泛型在IL和CLR本身中具有本機支持。在編譯泛型c#代碼時,像其他任何類型一樣,首先編譯器會將其編譯為IL,但是,IL只包含實際特定類型的參數或占位符,並有專用的IL指令支持泛型操作。泛型代碼的元數據中包含泛型信息。真正的泛型實例化工作是以“on-demand”的方式,發生在JIT編譯時。當進行JIT編譯時,JIT編譯器用指定的類型實參來替換泛型IL代碼元數據中的T,進行泛型類型的實例化。這會像JIT編譯器提供類型特定的IL元數據定義,就好像從未涉及到泛型一樣。JIT編譯器可以確保參數正確性,實施類型安全檢查,甚至執行類型特定的IntelliSense。
當.net將泛型IL代碼編譯為本機代碼時,所產生的本機代碼取決於指定的類型。JIT編譯器跟蹤已經生成特定類型的IL代碼,如果本機指定的是值類型,則JIT編譯器將泛型類型參數替換為指定的值類型,並且將其編譯為本機代碼。如果JIT編譯器用已經編譯為本機代碼的值類型編譯泛型IL代碼,則只返回對IL代碼的引用。因為JIT編譯器在以后的所有場合中都將使用相同的值類型特定的IL代碼,所以不會存在代碼膨脹問題。如果本機指定的是引用類型,則JIT編譯器將泛型IL嗲沒種的泛型參數替換為object,並將其編譯為本機代碼。在以后的任何針對引用類型而不是泛型類型參數的請求中,JIT編譯器只會重新使用實際代碼,使用該代碼,實例仍然按照它們離開托管堆的大小分配空間,並且不會存在強制類型轉換。
泛型實現:
C#泛型支持包括類、結構、接口、委托四種泛型類型,以及其方法成員。此次,只以泛型類做簡單演示:

class Program { static void Main(string[] args) { int obj = 1; Test<int> testInt = new Test<int>(obj); Console.WriteLine(testInt.obj);//1 string obj1 = "hello"; Test<string> testString = new Test<string>(obj1); Console.WriteLine(testString.obj);//hello Console.Read(); } } class Test<T> { public T obj; public Test(T obj) { this.obj = obj; } }
泛型約束:
在指定一個類型參數時,可以指定類型參數必須滿足的約束條件。這里通過指定類型參數時使用where子句來實現的。泛型約束有:基類約束、接口約束、構造函數約束、引用類型和值類型約束。下面簡單一一介紹:
基類約束:
使用基類約束,可以指定某個類型實參必須繼承的基類。
基類約束有兩個功能:
(1)它允許在泛型類中使用由約束指定的基類所定義的成員。例如,可以調用基類的方法或者使用基類的屬性。如果沒有基類約束,編譯器就無法知道某個類型實參擁有哪些成員。通過提供基類約束,編譯器將知道所有的類型實參都擁有由指定基類所定義的成員。
(2)確保類型實參支持指定的基類類型參數。這意味着對於任意給定的基類約束,類型實參必須要么是基類本身,要么是派生於該基類的類,如果試圖使用沒有繼承指定基類的類型實參,就會導致編譯錯誤。
基類約束使用下面形式的where子句:where T:base-class-name
T是類型參數的名稱,base-class-name是基類的名稱,這里只能指定一個基類。

class A { public void Func1() { } } class B { public void Func2() { } } class C<S, T> where S : A where T : B { public C(S s,T t) { //S的變量可以調用Func1方法 s.Func1(); //T的變量可以調用Func2方法 t.Func2(); } }
接口約束:
接口約束用於指定某個類型參數必須應用的接口。接口的兩個主要功能和基類約束完全一樣。基本形式 where T:interface-name
interface-name是接口的名稱,可以通過使用由逗號分割的列表來同時指定多個接口。如果某個約束同時包含基類和接口,則先指定基類列表,再指定接口列表。

interface IA<T> { T Func1(); } interface IB { void Func2(); } interface IC<T> { T Func3(); } class MyClass<T, V> where T : IA<T> where V : IB, IC<V> { public MyClass(T t,V v) { //T的對象可以調用Func1 t.Func1(); //V的對象可以調用Func2和Func3 v.Func2(); v.Func3(); } }
構造器約束:
new()構造函數約束允許開發人員實例化一個泛型類型的對象。
一般情況下,我們無法創建一個泛型類型參數的實例。然而,new()約束改變了這種情況,它要求類型參數必須提供一個無參數的構造函數。在使用new()約束時,可以通過調用該無參構造函數來創建對象。基本形式: where T : new()
使用new()約束時應注意兩點:
(1)它可以與其他約束一起使用,但是必須位於約束列表的末端。
(2)new()僅允許開發人員使用無參構造函數來構造一個對象,即使同時存在其他的構造函數。換句話說,不允許給類型參數的構造函數傳遞實參。

class A { public A() { } } class B { public B() { } public B(int i) { } } class C<T> where T : new() { T t; public C() { t = new T(); } } class D { public void Func() { C<A> c = new C<A>(); C<B> d = new C<B>(); } }
值類型和引用類型約束:
如果引用類型和值類型之間的差別對於泛型代碼非常重要,那么這些約束就非常有用。 基本形式: where T : class where T : struct 若同時存在其他約束的情況下,class或struct必須位於列表的開頭。另外可以通過 使用約束來建立兩個類型參數之間的關系 例如 class GenericClass2<T, V> where V:T{} -------- 要求V必須繼承於T,這種稱為裸類型約束(naked type constraint)。

public struct A { } public class B { } public class C<T> where T : struct { } C<A> c1 = new C<A>(); C<B> c2 = new C<B>();//error
泛型的優點:
泛型使代碼可以重用,類型和內部數據可以在不導致代碼膨脹的情況下進行更改,而不管是值類型還是引用類型。可以一次性地開發、測試和部署代碼,通過任何類型(包括將來的類型)來重用它,並且全部具有編譯器支持和類型安全。因為泛型代碼不會強行對值類型進行裝箱和取消裝箱,或者對引用類型進行向下強制類型轉換,所以性能得到顯著提高。對於值類型,性能通常會提高200%;對於引用類型,在訪問該類型時,也可以達到預期性。
簡單來說:使用泛型,可以極大地提高代碼的重用度,同時還可以獲得強類型的支持,避免了隱式的裝箱、拆箱,在一定程度上提升了應用程序的性能。
再簡單點:可重用、類型安全、高效率。
補充:
1、C#的泛型能力由CLR在運行時支持,它既不同於C++在編譯時所支持的靜態模板,也不同於Java在編譯器層面使用“擦拭法”支持的簡單的泛型。
2、C#的泛型支持包括類、結構、接口、委托四種泛型類型,以及方法成員。
3、C#的泛型采用“基類,接口,構造器,值類型/引用類型”的約束方式來實現對類型參數的“顯式約束”,它不支持C++模板那樣的基於簽名的隱式約束。
網上找到一個介紹泛型的好文章:
引用: