1.1.1 摘要
圖1 C# 泛型介紹
????? 在接觸泛型之前,我們編程一般都是使用具體類型(char, int, string等)或自定義類型來定義我們變量,如果我們有一個功能很強的接口,而且我們想把它提取或重構成一個通用的接口,使得該接口不僅僅適用于已定義數據類型,而是適用于更多數據類型,從而方便以后的擴展。
???? 泛型提供上述功能的實現,泛型其實就是提供一個數據類型的抽象層,因為它泛所以抽象,方便了我們代碼的重構和提取,我們無需hard-code接口中的數據類型,而是通過一個抽象泛型類型來指定數據類型,所以泛型可以提取出一個通用的接口。接下來讓我們通過具體的例子說明什么是泛型。
1.1.2 正文
1.1.1.1 沒有泛型的C# 1.0
????? 相信許多人都使用過棧或者其他經典數據結構,而且對于棧的FILO都是嫻熟于心了。假設現在要我們定義一個棧,用來存放具體數據類型,例如:int 數據,OK讓我們來實現自定義棧。
/// <summary> /// A custom stack1. /// </summary> public class CustomStack1 {private int _stackPointer = 0;private int[] _stackArray;public void Push(int item){Concrete implemention.}public int Pop(){Concrete implemention.} }
????? 為了簡潔沒有完全遵守OOP的原則,這里沒有使用屬性和給出方法的具體實現,現在我們有了第一版可以存放int數據類型的棧。但如果需求改變保存數據類型增加string。那么我們可以怎樣實現呢?沒錯我們只要增加一個類型為string的棧就OK了。
/// <summary> /// A custom stack2. /// </summary> public class CustomStack2 {private int _stackPointer = 0;private string[] _stackArray;public void Push(int item){Concrete implemention.}public string Pop(){Concrete implemention.} }
????? 很快我們就完成了第二版本的棧了,這看上去的確很簡單而且實現起來很快只不過是ctrl + c和ctrl + v,但我們的代碼真的那么簡單好維護嗎?想想其實危機四伏。我們能不能一開始就把保存的數據類型定義成為一個通用的類型呢(抽象數據類型)?----邪惡的object。
/// <summary> /// A custom stack. /// </summary> public class CustomStack {private int _stackPointer = 0;private object[] _stackArray;public void Push(object item){Concrete implemention.}public object Pop(){Concrete implemention.} }
????? 現在我們實現了第三版的棧了,看上去我們好像找到了通用的解決方法,想想我們要注意一點是如果我們保存和取出數據類型為值類型時,值類型要進行boxing和unboxing操作(《.NET值類型和引用類型101》),而且還有進行數據類型檢測。如果數據量不大我們可能察覺不到性能變化,但數據量大hehehe…。這就是為什么我們要使用泛型的原因之一,但更重要的原因是在進行boxing和unboxing時,我們需要提供更多信息給編譯器,編譯時再根據我們提供的信息進行類型檢測。
1.1.1.2 C#中的泛型
????? 現在我們知道了.NET為什么要提供泛型,現在讓我們學習什么是泛型和怎樣實現吧!在C# 2.0中提供了泛型這一機制,但這可不是什么新奇的技術,因為早在C++和Java都提供泛型機制。
C#中提供五種泛型分別是:classes, structs, interfaces, delegates, and methods。
- 泛型類
???? 也許有人說沒有使用過C++的模板或Java的泛型,而且不了解C#中的泛型,真的是這樣嗎?其實我們經常都在使用泛型,相信很多人都使用過Dictionary<TKey,TValue>,沒錯這就是C#中提供的泛型字典,接下來我們將介紹自定義泛型類CustomStack。
圖2泛型類?
/// <summary> /// A custom generic stack. /// </summary> public class CustomStack<T> {private int _stackPointer = 0;private T[] _stackArray;public void Push(T item){Concrete implemention.}public T Pop(){Concrete implemention.} }
????? 前面給出了泛型類CustomStack實現,我們發現只需在類名后添加<T>,并且把具體數據類型改為抽象的泛型類型T,現在我們就定義了一個簡單泛型類,我們可以通過以下對比一下非泛型和泛型實現的區別。
圖3非泛型和泛型類定義
????? 通過上圖我們發現泛型類CustomStack和非泛型類CustomStack在實現上沒有太大的區別,我們只需把具體類型換成T就OK了,接下來我們將進入泛型的進階學習。
- 泛型委托
???? 關于委托大家可以閱讀《.NET 中的委托》和《.NET中的委托和事件(續)》,現在讓我們學習一下泛型委托。
圖4泛型委托的定義
????? 上圖給出了泛型委托的定義,我們要注意Type parameters包括泛型參數和返回類型。隨著C#3.0 Linq特性的引用,泛型委托的使用得到了極大地擴展,讓我們通過具體代碼講講如何使用泛型委托。
????? 首先我們定義一個泛型委托,然后再定義一個Calc類,接著在Calc里定義兩個方法分別是Add() 和Divide() 如下:
/// <summary> /// Define generic delegate. /// </summary> public delegate TR CalcMethod<T1, T2, TR>(T1 x, T2 y);public class Calc {static private double sum;/// <summary>/// Gets or sets the sum./// </summary>/// <value>/// The sum./// </value>static public double Sum{get { return sum; }set { sum = value; }}/// <summary>/// Adds the specified x./// </summary>/// <param name="x">The x.</param>/// <param name="y">The y.</param>/// <returns></returns>static public string Add(int x , int y){Sum = x + y;return Sum.ToString();}/// <summary>/// Divides the specified x./// </summary>/// <param name="x">The x.</param>/// <param name="y">The y.</param>/// <returns></returns>static public string Divide(int x, int y){if (y == 0){return "除數不能為零";}Sum = x * 1.0 / y;return Sum.ToString();}}
??? 接下來我們需要定義一個委托變量,而后將委托變量和具體調用方法綁定就OK了。
Initialize engeric delegate instance to Add method. CalcMethod<int, int, string> calcMethod =new CalcMethod<int, int, string>(Calc.Add);
- 類型約束
??? 前面例子我們并沒有對泛型類型進行限制,由于T是一個抽象類型,有時我們必須限制T是哪種類型,例如我們可以定義CustomStack<int>,CustomStack<double>或CustomStack<string>類型的棧,但有時我們必須限制泛型類型。這時我們需要使用類型約束,引入關鍵字----where。
??? C#中提供五種類型約束(類名,接口名,引用類型,值類型和構造函數),我們可以通過使用不同的類型約束來限定泛型類型。
類型約束 | 約束描述 |
ClassName | T只能是該類或繼承該類的子類 |
InterfaceName | T只能是該接口或實行該接口的類型 |
struct | T是任意值類型 |
class | T是任意引用類型(如classes, arrays, delegates, and interfaces等) |
new() | T是包含公有無參構成函數的任意類型 |
表1類型約束
?
????? 通過表1我們初步地了解不同類型約束之間的區別,現在讓我們通過具體的例子來看看它們使用范圍和區別。
????? 假設我們定義了一個泛型結構體struct RefSample<T> where T : class,現在考考大家以下例子符合類約束的有(提示值類型和引用類型區別):
?
A. ? RefSample<IDisposable>
B. ? RefSample<string>
C.? RefSample<int[]>
D.? RefSample<Guid>
E. ? RefSample<int>
???? 激動人心的時刻又到了現在讓我們快快公布答案吧!答案是ABC,因為該泛型的約束是引用類型約束,如果大家還不清楚請看一下引用類型約束的定義。
???? 通過類約束例子,我們舉一反三值類型約束就是約束類型為值類型。
???? OK接下來讓我們介紹構造函數約束。在介紹構造函數約束之前,讓我們回顧一下符合構造函數約束的條件:任意值類型,任意非靜態非抽象無明確定義構造函數的類和任意非抽象有明確定義無參構造函數的類。這句話很別扭,讓我們通過具體例子講解一下。
Define generic methodWhich has constructor type constraints public T CreateInstance<T>() where T : new() { return new T(); }CreateInstance<int>(); CreateInstance<object>(); CreateInstance<string>();
????? 現在我們定義了一個泛型方法并且添加構造函數約束,調用CreateInstance<int>() 和 CreateInstance<object>() 成功,但調用CreateInstance<string>() 失敗,這由于String類沒有提供無參構造函數。也許有人會問:“我們很難判斷每種類型是否包含默認構無參造函數”,確確是這樣但我們可以記住的是C#規定值類型提供默認無參構造函數。
?
- 組合約束
???? 前面的泛型例子都是只使用一種類型約束,但有時候我們要使用多種類型約束,這時我們就可以使用組合約束,在使用約束的時我們要注意約束的先后次序,約束次序如下:

圖5約束次序
???? 通過上圖我們發現ClassName,class和struct是主要約束,接口名是第二約束,最后是構造函數約束。OK現在我們對組合約束有了初步地了解,那么讓我們通過以下例子加深理解。
A. class Sample<T> where T : class, struct
B. class Sample<T> where T : Stream, class
C. class Sample<T> where T : new(), Stream
D. class Sample<T> where T : IDisposable, Stream
E. class Sample<T> where T : XmlReader, IComparable, IComparable
F. class Sample<T,U> where T : struct where U : class, T
G. class Sample<T,U> where T : Stream, U : IDisposable
???? 上面給出錯誤使用組合約束的例子(注:A~E單類型組合約束,FG多類型組合約束),請大家指出約束錯誤的原因,如果不清楚錯誤的原因大家回憶一下約束定義。
1.1.3 總結
???? 本文注意介紹了C# 中泛型的基礎知識給出自己的總結,而且這不是C# 泛型的全部,如果大家在看完本文之后希望進一步學習泛型這才是我的目的。
泛型的優點:
- 編譯時進行類型檢測,減少運行時異常InvalidCastException。
- 泛型使用強數據類型。如我們實例一個CustomStack<CustomStruct>對象objStack,通過調用Push()方法成功把CustomStruct壓入棧中,而objStack.Push(“Stack”)失敗。
- 值類型無需進行boxing和unboxing操作。例如前面的泛型Push()和Pop()方法存放和取出值類型時無需進行boxing和unboxing操作。
- 減少代碼量。如:我們定義一個泛型棧,就不用像前面例子那樣分別定義int和string類型的棧。
- 程序性能提高,因為無需類型轉換,從而減少類型檢測。
- 使用泛型減少內存消耗。因為無需進行boxing操作,不用重新分配堆空間。
- 代碼可讀性更強。
????? 很多人喜歡將C# ,C++ 和Java中的泛型進行對比,而且也有很多相關的討論,在這里我也給出自己的想法,我覺得C++ 的泛型功能的確比C# 和Java都要強大。

[作者]:JK_Rush
[出處]:http://www.cnblogs.com/rush/
[本文基于]:?署名-非商業性使用 3.0?許可協議發布,歡迎轉載,演繹,但是必須保留本文的署名?JK_Rush?(包含鏈接),且不得用于商業目的。如您有任何疑問或者授權方面的協商,請與我聯系 。