2.0版C#語言和公共語言運行時(CLR)中增長了泛型。泛型將類型參數的概念引入.NETFramework,類型參數使得設計以下類和方法成爲可能:這些類和方法將一個或多個類型的指定推遲到客戶端代碼聲明並實例化該類或方法的時候。例如,經過使用泛型類型參數T,您能夠編寫其餘客戶端代碼可以使用的單個類,而不致引入運行時強制轉換或裝箱操做的成本或風險,以下所示:node
//Declarethegenericclass.編程
publicclassGenericList<T>設計模式
{數組
voidAdd(Tinput){}安全
}app
classTestGenericListless
{ide
privateclassExampleClass{}函數
staticvoidMain()性能
{
//Declarealistoftypeint.
GenericList<int>list1=newGenericList<int>();
//Declarealistoftypestring.
GenericList<string>list2=newGenericList<string>();
//DeclarealistoftypeExampleClass.
GenericList<ExampleClass>list3=newGenericList<ExampleClass>();
}
}
泛型概述
在公共語言運行時和 C# 語言的早期版本中,通用化是經過在類型與通用基類型 Object 之間進行強制轉換來實現的,泛型提供了針對這種限制的解決方案。 經過建立泛型類,您能夠建立一個在編譯時類型安全的集合。
使用非泛型集合類的限制能夠經過編寫一小段程序來演示,該程序使用 .NET Framework 類庫中的 ArrayList 集合類。 ArrayList 是一個使用起來很是方便的集合類,無需進行修改便可用來存儲任何引用或值類型。
// The .NET Framework 1.1 way to create a list:
System.Collections.ArrayList list1 = new System.Collections.ArrayList();
list1.Add(3);
list1.Add(105);
System.Collections.ArrayList list2 = new System.Collections.ArrayList();
list2.Add("It is raining in Redmond.");
list2.Add("It is snowing in the mountains.");
但這種方即是須要付出代價的。 添加到 ArrayList 中的任何引用或值類型都將隱式地向上強制轉換爲 Object。 若是項是值類型,則必須在將其添加到列表中時進行裝箱操做,在檢索時進行取消裝箱操做。 強制轉換以及裝箱和取消裝箱操做都會下降性能;在必須對大型集合進行循環訪問的狀況下,裝箱和取消裝箱的影響很是明顯。 (附:裝箱是將值類型轉換爲 object 類型或由此值類型實現的任一接口類型的過程。 當 CLR 對值類型進行裝箱時,會將該值包裝到 System.Object 內部,再將後者存儲在託管堆上。 取消裝箱將從對象中提取值類型。 裝箱是隱式的;取消裝箱是顯式的。 裝箱和取消裝箱的概念是類型系統 C# 統一視圖的基礎,其中任一類型的值都被視爲一個對象。相對於簡單的賦值而言,裝箱和取消裝箱過程須要進行大量的計算。 對值類型進行裝箱時,必須分配並構造一個新對象。 次之,取消裝箱所需的強制轉換也須要進行大量的計算。)
另外一個限制是缺乏編譯時類型檢查;由於 ArrayList 會將全部項都強制轉換爲 Object,因此在編譯時沒法防止客戶端代碼執行相似以下的操做:
System.Collections.ArrayList list = new System.Collections.ArrayList();
// Add an integer to the list.
list.Add(3);
// Add a string to the list. This will compile, but may cause an error later.
list.Add("It is raining in Redmond.");
int t = 0;
// This causes an InvalidCastException to be returned.
foreach (int x in list)
{
t += x;
}
儘管將字符串和 ints 組合在一個 ArrayList 中的作法在建立異類集合時是徹底可接受的,而且有時須要有意爲之,但這種作法極可能產生編程錯誤,而且直到運行時才能檢測到此錯誤。
在 C# 語言的 1.0 和 1.1 版本中,只能經過編寫本身的特定於類型的集合來避免 .NET Framework 基類庫集合類中的通用代碼的危險。 固然,因爲此類不可對多個數據類型重用,所以將喪失通用化的優勢,而且您必須對要存儲的每一個類型從新編寫該類。
ArrayList 和其餘類似類真正須要的是:客戶端代碼基於每一個實例指定這些類要使用的具體數據類型的方式。 這樣將再也不須要向上強制轉換爲 T:System.Object,同時,也使得編譯器能夠進行類型檢查。 換句話說,ArrayList 須要一個類型參數。 這正是泛型所能提供的。 在 N:System.Collections.Generic 命名空間的泛型 List<T> 集合中,向集合添加項的操做相似於如下形式:
// The .NET Framework 2.0 way to create a list
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 更安全而且速度更快的列表,對於列表項是值類型的狀況尤其如此。
在泛型類型或方法定義中,類型參數是客戶端在實例化泛型類型的變量時指定的特定類型的佔位符。 泛型類(如 泛型介紹(C# 編程指南) 中列出的 GenericList<T>)不能夠像這樣使用,由於它實際上並非一個類型,而更像是一個類型的藍圖。 若要使用 GenericList<T>,客戶端代碼必須經過指定尖括號中的類型參數來聲明和實例化構造類型。 此特定類的類型參數能夠是編譯器識別的任何類型。 能夠建立任意數目的構造類型實例,每一個實例使用不一樣的類型參數,以下所示:
GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
在每一個 GenericList<T> 實例中,類中出現的每一個 T 都會在運行時替換爲相應的類型參數。 經過這種替換方式,咱們使用一個類定義建立了三個獨立的類型安全的有效對象。 有關 CLR 如何執行此替換的更多信息,請參見運行時中的泛型(C# 編程指南)。
類型參數命名準則
public interface ISessionChannel<TSession> { /*...*/ }
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }
public int IComparer<T>() { return 0; }
public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T : struct { /*...*/ }
public interface ISessionChannel<TSession>
{
TSession Session { get; }
}
在定義泛型類時,能夠對客戶端代碼可以在實例化類時用於類型參數的類型種類施加限制。 若是客戶端代碼嘗試使用某個約束所不容許的類型來實例化類,則會產生編譯時錯誤。 這些限制稱爲約束。 約束是使用 where 上下文關鍵字指定的。 下表列出了六種類型的約束:
約束 |
說明 |
T:結構 |
類型參數必須是值類型。 能夠指定除 Nullable 之外的任何值類型。 有關更多信息,請參見 使用能夠爲 null 的類型(C# 編程指南)。 |
T:類 |
類型參數必須是引用類型;這一點也適用於任何類、接口、委託或數組類型。 |
T:new() |
類型參數必須具備無參數的公共構造函數。 當與其餘約束一塊兒使用時,new() 約束必須最後指定。 |
T:<基類名> |
類型參數必須是指定的基類或派生自指定的基類。 |
T:<接口名稱> |
類型參數必須是指定的接口或實現指定的接口。 能夠指定多個接口約束。 約束接口也能夠是泛型的。 |
T:U |
爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數。 |
使用約束的緣由
若是要檢查泛型列表中的某個項以肯定它是否有效,或者將它與其餘某個項進行比較,則編譯器必須在必定程度上保證它須要調用的運算符或方法將受到客戶端代碼可能指定的任何類型參數的支持。 這種保證是經過對泛型類定義應用一個或多個約束得到的。 例如,基類約束告訴編譯器:僅此類型的對象或今後類型派生的對象纔可用做類型參數。 一旦編譯器有了這個保證,它就可以容許在泛型類中調用該類型的方法。 約束是使用上下文關鍵字 where 應用的。 下面的代碼示例演示可經過應用基類約束添加到 GenericList<T> 類(在泛型介紹(C# 編程指南)中)的功能。
public class Employee
{
private string name;
private int id;
public Employee(string s, int i)
{
name = s;
id = i;
}
public string Name
{
get { return name; }
set { name = value; }
}
public int ID
{
get { return id; }
set { id = value; }
}
}
public class GenericList<T> where T : Employee
{
private class Node
{
private Node next;
private T data;
public Node(T t)
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
public T Data
{
get { return data; }
set { data = value; }
}
}
private Node head;
public GenericList() //constructor
{
head = null;
}
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;
}
}
public T FindFirstOccurrence(string s)
{
Node current = head;
T t = null;
while (current != null)
{
//The constraint enables access to the Name property.
if (current.Data.Name == s)
{
t = current.Data;
break;
}
else
{
current = current.Next;
}
}
return t;
}
}
約束使得泛型類可以使用 Employee.Name 屬性,由於類型爲 T 的全部項都保證是 Employee 對象或從 Employee 繼承的對象。
能夠對同一類型參數應用多個約束,而且約束自身能夠是泛型類型,以下所示:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}
經過約束類型參數,能夠增長約束類型及其繼承層次結構中的全部類型所支持的容許操做和方法調用的數量。 所以,在設計泛型類或方法時,若是要對泛型成員執行除簡單賦值以外的任何操做或調用 System.Object 不支持的任何方法,您將須要對該類型參數應用約束。
在應用 where T : class 約束時,避免對類型參數使用 == 和 != 運算符,由於這些運算符僅測試引用同一性而不測試值相等性。 即便在用做參數的類型中重載這些運算符也是如此。 下面的代碼說明了這一點;即便 String 類重載 == 運算符,輸出也爲 false。
public static void OpTest<T>(T s, T t) where T : class
{
System.Console.WriteLine(s == t);
}
static void Main()
{
string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();
OpTest<string>(s1, s2);
}
這種狀況的緣由在於,編譯器在編譯時僅知道 T 是引用類型,所以必須使用對全部引用類型都有效的默認運算符。 若是必須測試值相等性,建議的方法是同時應用 where T : IComparable<T> 約束,並在將用於構造泛型類的任何類中實現該接口。
約束多個參數
能夠對多個參數應用約束,並對一個參數應用多個約束,以下面的示例所示:
class Base { }
class Test<T, U>
where U : struct
where T : Base, new() { }
未綁定的類型參數
沒有約束的類型參數(如公共類 SampleClass<T>{} 中的 T)稱爲未綁定的類型參數。未綁定的類型參數具備如下規則:
做爲約束的類型參數
將泛型類型參數做爲約束使用,在具備本身類型參數的成員函數必須將該參數約束爲包含類型的類型參數時很是有用,以下示例所示:
class List<T>
{
void Add<U>(List<U> items) where U : T {/*...*/}
}
在上面的示例中,T 在 Add 方法的上下文中是一個類型約束,而在 List 類的上下文中是一個未綁定的類型參數。
類型參數還可在泛型類定義中用做約束。 請注意,必須在尖括號中聲明此類型參數與任何其餘類型的參數:
//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }
泛型類的類型參數約束的做用很是有限,由於編譯器除了假設類型參數派生自 System.Object 之外,不會作其餘任何假設。 在但願強制兩個類型參數之間的繼承關係的狀況下,可對泛型類使用參數類型約束。
泛型類封裝不是特定於具體數據類型的操做。 泛型類最經常使用於集合,如連接列表、哈希表、堆棧、隊列、樹等。 像從集合中添加和移除項這樣的操做都以大致上相同的方式執行,與所存儲數據的類型無關。
對於大多數須要集合類的方案,推薦的方法是使用 .NET Framework 類庫中所提供的類。 有關使用這些類的更多信息,請參見.NET Framework 類庫中的泛型(C# 編程指南)。
通常狀況下,建立泛型類的過程爲:從一個現有的具體類開始,逐一將每一個類型更改成類型參數,直至達到通用化和可用性的最佳平衡。建立您本身的泛型類時,須要特別注意如下事項:
一般,可以參數化的類型越多,代碼就會變得越靈活,重用性就越好。 可是,太多的通用化會使其餘開發人員難以閱讀或理解代碼。
一條有用的規則是,應用盡量最多的約束,但仍使您可以處理必須處理的類型。 例如,若是您知道您的泛型類僅用於引用類型,則應用類約束。 這能夠防止您的類被意外地用於值類型,並容許您對 T 使用 as 運算符以及檢查空值。
因爲泛型類能夠做爲基類使用,此處適用的設計注意事項與非泛型類相同。 請參見本主題後面有關從泛型基類繼承的規則。
例如,若是您設計一個類,該類將用於建立基於泛型的集合中的項,則可能必須實現一個接口,如 IComparable<T>,其中 T 是您的類的類型。
有關簡單泛型類的示例,請參見泛型介紹(C# 編程指南)。
類型參數和約束的規則對於泛型類行爲有幾方面的含義,特別是關於繼承和成員可訪問性。 您應當先理解一些術語,而後再繼續進行。 對於泛型類 Node<T>,客戶端代碼可經過指定類型參數來引用該類,以便建立封閉式構造類型 (Node<int>)。 或者可讓類型參數處於未指定狀態(例如在指定泛型基類時)以建立開放式構造類型 (Node<T>)。 泛型類能夠從具體的、封閉式構造或開放式構造基類繼承:
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }
//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }
非泛型類(換句話說,即具體類)能夠從封閉式構造基類繼承,但沒法從開放式構造類或類型參數繼承,由於在運行時客戶端代碼沒法提供實例化基類所需的類型參數。
//No error
class Node1 : BaseNodeGeneric<int> { }
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}
//Generates an error
//class Node3 : T {}
從開放式構造類型繼承的泛型類必須爲任何未被繼承類共享的基類類型參數提供類型變量,如如下代碼所示:
class BaseNodeMultiple<T, U> { }
//No error
class Node4<T> : BaseNodeMultiple<T, int> { }
//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }
//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}
從開放式構造類型繼承的泛型類必須指定約束,這些約束是基類型約束的超集或暗示基類型約束:
class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }
泛型類型可使用多個類型參數和約束,以下所示:
class SuperKeyType<K, V, U>
where U : System.IComparable<U>
where V : new()
{ }
開放式構造類型和封閉式構造類型能夠用做方法參數:
void Swap<T>(List<T> list1, List<T> list2)
{
//code to swap items
}
void Swap(List<int> list1, List<int> list2)
{
//code to swap items
}
若是某個泛型類實現了接口,則能夠將該類的全部實例強制轉換爲該接口。
泛型類是不變的。 也就是說,若是輸入參數指定 List<BaseClass>,則當您嘗試提供 List<DerivedClass> 時,將會發生編譯時錯誤。
爲泛型集合類或表示集合中項的泛型類定義接口一般頗有用。 對於泛型類,使用泛型接口十分可取,例如使用 IComparable<T> 而不使用 IComparable,這樣能夠避免值類型的裝箱和取消裝箱操做。 .NET Framework 類庫定義了若干泛型接口,以用於 System.Collections.Generic 命名空間中的集合類。
將接口指定爲類型參數的約束時,只能使用實現此接口的類型。 下面的代碼示例顯示從 SortedList<T> 類派生的 GenericList<T> 類。 有關更多信息,請參見 泛型介紹(C# 編程指南)。 SortedList<T> 添加約束 where T : IComparable<T>。 這將使 SortedList<T> 中的 BubbleSort 方法可以對列表元素使用泛型 CompareTo 方法。 在此示例中,列表元素爲簡單類,即實現 Person 的 IComparable<Person>。
//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
protected Node head;
protected Node current = null;
// Nested class is also generic on T
protected class Node
{
public Node next;
private T data; //T as private member datatype
public Node(T t) //T used in non-generic constructor
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
public T Data //T as return type of property
{
get { return data; }
set { data = value; }
}
}
public GenericList() //constructor
{
head = null;
}
public void AddHead(T t) //T as method parameter type
{
Node n = new Node(t);
n.Next = head;
head = n;
}
// Implementation of the iterator
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
// IEnumerable<T> inherits from IEnumerable, therefore this class
// must implement both the generic and non-generic versions of
// GetEnumerator. In most cases, the non-generic method can
// simply call the generic method.
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
// A simple, unoptimized sort algorithm that
// orders list elements from lowest to highest:
public void BubbleSort()
{
if (null == head || null == head.Next)
{
return;
}
bool swapped;
do
{
Node previous = null;
Node current = head;
swapped = false;
while (current.next != null)
{
// Because we need to call this method, the SortedList
// class is constrained on IEnumerable<T>
if (current.Data.CompareTo(current.next.Data) > 0)
{
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}
// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
string name;
int age;
public Person(string s, int i)
{
name = s;
age = i;
}
// This will cause list elements to be sorted on age values.
public int CompareTo(Person p)
{
return age - p.age;
}
public override string ToString()
{
return name + ":" + age;
}
// Must implement Equals.
public bool Equals(Person p)
{
return (this.age == p.age);
}
}
class Program
{
static void Main()
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();
//Create name and age values to initialize Person objects.
string[] names = new string[]
{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
//Populate the list.
for (int x = 0; x < 10; x++)
{
list.AddHead(new Person(names[x], ages[x]));
}
//Print out unsorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with unsorted list");
//Sort the list.
list.BubbleSort();
//Print out sorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with sorted list");
}
}
可將多重接口指定爲單個類型上的約束,以下所示:
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}
一個接口可定義多個類型參數,以下所示:
interface IDictionary<K, V>
{
}
適用於類的繼承規則一樣適用於接口:
interface IMonth<T> { }
interface IJanuary : IMonth<int> { } //No error
interface IFebruary<T> : IMonth<int> { } //No error
interface IMarch<T> : IMonth<T> { } //No error
//interface IApril<T> : IMonth<T, U> {} //Error
若是泛型接口爲逆變的,即僅使用其類型參數做爲返回值,則此泛型接口能夠從非泛型接口繼承。 在 .NET Framework 類庫中,IEnumerable<T> 從 IEnumerable 繼承,由於 IEnumerable<T> 只在 GetEnumerator 的返回值和 Current 屬性 getter 中使用 T。
具體類能夠實現已關閉的構造接口,以下所示:
interface IBaseInterface<T> { }
class SampleClass : IBaseInterface<string> { }
只要類參數列表提供了接口必需的全部參數,泛型類即可以實現泛型接口或已關閉的構造接口,以下所示:
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
class SampleClass1<T> : IBaseInterface1<T> { } //No error
class SampleClass2<T> : IBaseInterface2<T, string> { } //No error
對於泛型類、泛型結構或泛型接口中的方法,控制方法重載的規則相同。 有關更多信息,請參見泛型方法(C# 編程指南)。
泛型方法是使用類型參數聲明的方法,以下所示:
static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
下面的代碼示例演示一種使用 int 做爲類型參數的方法調用方式:
public static void TestSwap()
{
int a = 1;
int b = 2;
Swap<int>(ref a, ref b);
System.Console.WriteLine(a + " " + b);
}
也能夠省略類型參數,編譯器將推斷出該參數。 下面對 Swap 的調用等效於前面的調用:
Swap(ref a, ref b);
相同的類型推理規則也適用於靜態方法和實例方法。編譯器可以根據傳入的方法實參推斷類型形參;它沒法僅從約束或返回值推斷類型形參。所以,類型推理不適用於沒有參數的方法。類型推理在編譯時、編譯器嘗試解析重載方法簽名以前進行。編譯器向共享相同名稱的全部泛型方法應用類型推理邏輯。在重載解析步驟中,編譯器僅包括類型推理取得成功的那些泛型方法。
在泛型類中,非泛型方法能夠訪問類級別類型參數,以下所示:
class SampleClass<T>
{
void Swap(ref T lhs, ref T rhs) { }
}
若是定義採用相同類型參數做爲包含類的泛型方法,編譯器將生成警告 CS0693,由於在方法範圍內爲內部 T 提供的參數隱藏了爲外部 T 提供的參數。若是須要使用其餘類型參數(而不是實例化類時提供的類型參數)來靈活地調用泛型類方法,請考慮爲方法的類型參數提供另外一個標識符,以下面示例的 GenericList2<T> 中所示。
class GenericList<T>
{
// CS0693
void SampleMethod<T>() { }
}
class GenericList2<T>
{
//No warning
void SampleMethod<U>() { }
}
使用約束對方法中的類型參數啓用更專門的操做。此版本的 Swap<T> 如今名爲 SwapIfGreater<T>,它只能與實現 IComparable<T> 的類型參數一塊兒使用。
void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{
T temp;
if (lhs.CompareTo(rhs) > 0)
{
temp = lhs;
lhs = rhs;
rhs = temp;
}
}
泛型方法可使用許多類型參數進行重載。 例如,下列方法能夠所有位於同一個類中:
void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }
在 C# 2.0 以及更高版本中,下限爲零的一維數組自動實現 IList<T>。這使您能夠建立可以使用相同代碼循環訪問數組和其餘集合類型的泛型方法。此技術主要對讀取集合中的數據頗有用。IList<T> 接口不能用於在數組中添加或移除元素。若是嘗試對此上下文中的數組調用 IList<T> 方法(例如 RemoveAt),則將引起異常。
下面的代碼示例演示帶有 IList<T> 輸入參數的單個泛型方法如何同時循環訪問列表和數組,本例中爲整數數組。
class Program
{
static void Main()
{
int[] arr = { 0, 1, 2, 3, 4 };
List<int> list = new List<int>();
for (int x = 5; x < 10; x++)
{
list.Add(x);
}
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
static void ProcessItems<T>(IList<T> coll)
{
// IsReadOnly returns True for the array and False for the List.
System.Console.WriteLine
("IsReadOnly returns {0} for this collection.",
coll.IsReadOnly);
// The following statement causes a run-time exception for the
// array, but not for the List.
//coll.RemoveAt(4);
foreach (T item in coll)
{
System.Console.Write(item.ToString() + " ");
}
System.Console.WriteLine();
}
}
委託 能夠定義本身的類型參數。引用泛型委託的代碼能夠指定類型參數以建立已關閉的構造類型,就像實例化泛型類或調用泛型方法同樣,以下例所示:
public delegate void Del<T>(T item);
public static void Notify(int i) { }
Del<int> m1 = new Del<int>(Notify);
C# 2.0 版具備稱爲方法組轉換的新功能,此功能適用於具體委託類型和泛型委託類型,並使您可使用以下簡化的語法寫入上一行:
Del<int> m2 = Notify;
在泛型類內部定義的委託使用泛型類類型參數的方式能夠與類方法所使用的方式相同。
class Stack<T>
{
T[] items;
int index;
public delegate void StackDelegate(T[] items);
}
引用委託的代碼必須指定包含類的類型變量,以下所示:
private static void DoWork(float[] items) { }
public static void TestStack()
{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
}
根據典型設計模式定義事件時,泛型委託尤爲有用,由於發送方參數能夠爲強類型,再也不須要強制轉換成 Object,或反向強制轉換。
delegate void StackEventHandler<T, U>(T sender, U eventArgs);
class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
protected virtual void OnStackChanged(StackEventArgs a)
{
stackEvent(this, a);
}
}
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}
public static void Test()
{
Stack<double> s = new Stack<double>();
SampleClass o = new SampleClass();
s.stackEvent += o.HandleStackChange;
}
在泛型類和泛型方法中產生的一個問題是,在預先未知如下狀況時,如何將默認值分配給參數化類型 T:
給定參數化類型 T 的一個變量 t,只有當 T 爲引用類型時,語句 t = null 纔有效;只有當 T 爲數值類型而不是結構時,語句 t = 0 才能正常使用。解決方案是使用 default 關鍵字,此關鍵字對於引用類型會返回 null,對於數值類型會返回零。對於結構,此關鍵字將返回初始化爲零或 null 的每一個結構成員,具體取決於這些結構是值類型仍是引用類型。對於能夠爲 null 的值類型,默認返回 System.Nullable<T>,它像任何結構同樣初始化。
如下來自 GenericList<T> 類的示例顯示瞭如何使用 default 關鍵字。 有關更多信息,請參見泛型概述。
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Test with a non-empty list of integers.
GenericList<int> gll = new GenericList<int>();
gll.AddNode(5);
gll.AddNode(4);
gll.AddNode(3);
int intVal = gll.GetLast();
// The following line displays 5.
System.Console.WriteLine(intVal);
// Test with an empty list of integers.
GenericList<int> gll2 = new GenericList<int>();
intVal = gll2.GetLast();
// The following line displays 0.
System.Console.WriteLine(intVal);
// Test with a non-empty list of strings.
GenericList<string> gll3 = new GenericList<string>();
gll3.AddNode("five");
gll3.AddNode("four");
string sVal = gll3.GetLast();
// The following line displays five.
System.Console.WriteLine(sVal);
// Test with an empty list of strings.
GenericList<string> gll4 = new GenericList<string>();
sVal = gll4.GetLast();
// The following line displays a blank line.
System.Console.WriteLine(sVal);
}
}
// T is the type of data stored in a particular instance of GenericList.
public class GenericList<T>
{
private class Node
{
// Each node has a reference to the next node in the list.
public Node Next;
// Each node holds a value of type T.
public T Data;
}
// The list is initially empty.
private Node head = null;
// Add a node at the beginning of the list with t as its data value.
public void AddNode(T t)
{
Node newNode = new Node();
newNode.Next = head;
newNode.Data = t;
head = newNode;
}
// The following method returns the data value stored in the last node in
// the list. If the list is empty, the default value for type T is
// returned.
public T GetLast()
{
// The value of temp is returned as the value of the method.
// The following declaration initializes temp to the appropriate
// default value for type T. The default value is returned if the
// list is empty.
T temp = default(T);
Node current = head;
while (current != null)
{
temp = current.Data;
current = current.Next;
}
return temp;
}
}
}
將泛型類型或方法編譯爲 Microsoft 中間語言 (MSIL) 時,它包含將其標識爲具備類型參數的元數據。 泛型類型的 MSIL 的使用因所提供的類型參數是值類型仍是引用類型而不一樣。
第一次用值類型做爲參數來構造泛型類型時,運行時會建立專用泛型類型,將提供的參數代入到 MSIL 中的適當位置。 對於每一個用做參數的惟一值類型,都會建立一次專用泛型類型。
例如,假設您的程序代碼聲明瞭一個由整數構造的堆棧:
Stack<int> stack;
在此位置,運行時生成 Stack<T> 類的專用版本,並相應地用整數替換其參數。 如今,只要程序代碼使用整數堆棧,運行時就會重用生成的專用 Stack<T> 類。 在下面的示例中,建立了整數堆棧的兩個實例,它們共享 Stack<int> 代碼的單個實例:
Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();
可是,假定在代碼中的另外一個位置建立了使用不一樣值類型(好比 long 或用戶定義的結構)做爲其參數的另外一個 Stack<T> 類。 所以,運行時將生成另外一個版本的泛型類型,並在 MSIL 中的適當位置替換 long。 因爲每一個專用泛型類自己就包含值類型,所以再也不須要轉換。
對於引用類型,泛型的工做方式略有不一樣。 第一次使用任何引用類型構造泛型類型時,運行時會建立專用泛型類型,用對象引用替換 MSIL 中的參數。 而後,每次使用引用類型做爲參數來實例化構造類型時,不管引用類型的具體類型是什麼,運行時都會重用之前建立的泛型類型的專用版本。 之因此能夠這樣,是由於全部引用的大小相同。
例如,假設您有兩個引用類型:一個 Customer 類和一個 Order 類,而且同時假設您建立了一個 Customer 類型的堆棧:
class Customer { }
class Order { }
Stack<Customer> customers;
此時,運行時將生成 Stack<T> 類的一個專用版本,該版本存儲稍後將填寫的對象引用,而不是存儲數據。 假設下一行代碼建立另外一個引用類型的堆棧,該堆棧名爲 Order:
Stack<Order> orders = new Stack<Order>();
不一樣於值類型,對於 Order 類型不建立 Stack<T> 類的另外一個專用版本。 而是建立 Stack<T> 類的一個專用版本實例,並將 orders 變量設置爲引用它。 假設接下來您遇到一行建立 Customer 類型堆棧的代碼:
customers = new Stack<Customer>();
與前面使用 Order 類型建立的 Stack<T> 類同樣,建立了專用 Stack<T> 類的另外一個實例。 包含在其中的指針設置爲引用 Customer 類型大小的內存區域。 由於引用類型的數量會隨程序的不一樣而大幅變化,C# 泛型實現將編譯器爲引用類型的泛型類建立的專用類的數量減少到一個,從而大幅減少代碼量。
此外,使用值類型或引用類型參數實例化泛型 C# 類時,反射能夠在運行時查詢它,而且能夠肯定它的實際類型及其類型參數。
由於公共語言運行時 (CLR) 可以在運行時訪問泛型類型信息,因此可使用反射獲取關於泛型類型的信息,方法與用於非泛型類型的方法相同。 有關更多信息,請參見運行時中的泛型(C# 編程指南)。
在 .NET Framework 2.0 中,有幾個新成員添加到了 Type 類中,用以啓用泛型類型的運行時信息。 請參見有關這些類的文檔來了解有關如何使用這些方法和屬性的更多信息。System.Reflection.Emit 命名空間還包含支持泛型的新成員。 請參見 如何:用反射發出定義泛型類型。
有關泛型反射中使用的術語的固定條件列表,請參見 IsGenericType 屬性備註。
System.Type 成員名稱 |
說明 |
若是類型爲泛型,則返回 true。 |
|
返回 Type 對象數組,這些對象表示爲構造類型提供的類型變量,或泛型類型定義的類型參數。 |
|
返回當前構造類型的基礎泛型類型定義。 |
|
返回表示當前泛型類型參數約束的 Type 對象的數組。 |
|
若是類型或其任意封閉類型或方法包含沒有被提供特定類型的類型參數,則返回 true。 |
|
獲取 GenericParameterAttributes 標誌的組合,這些標誌描述當前泛型類型參數的特殊約束。 |
|
對於表示類型參數的 Type 對象,獲取類型參數在聲明該類型參數的泛型類型定義或泛型方法定義的類型參數列表中的位置。 |
|
獲取一個值,該值指示當前 Type 是表示泛型類型定義的類型參數,仍是泛型方法定義的類型參數。 |
|
獲取一個值,該值指示當前 Type 是否表示能夠用來構造其餘泛型類型的泛型類型定義。 若是類型表示泛型類型的定義,則返回 true。 |
|
返回定義當前泛型類型參數的泛型方法;若是類型參數不是由泛型方法定義的,則返回空值。 |
|
用類型數組的元素替代當前泛型類型定義的類型參數,並返回表示結果構造類型的 Type 對象。 |
此外,MethodInfo 類中還添加了新成員以啓用泛型方法的運行時信息。 有關泛型方法反射中使用的術語的固定條件列表,請參見 IsGenericMethod 屬性備註。
System.Reflection.MemberInfo 成員名稱 |
說明 |
若是方法爲泛型,則返回 true。 |
|
返回 Type 對象數組,這些對象表示構造泛型方法的類型變量,或泛型方法定義的類型參數。 |
|
返回當前構造方法的基礎泛型方法定義。 |
|
若是方法或其任意封閉類型包含沒有被提供特定類型的任何類型參數,則返回 true。 |
|
若是當前 MethodInfo 表示泛型方法的定義,則返回 true。 |
|
用類型數組的元素替代當前泛型方法定義的類型參數,並返回表示結果構造方法的 MethodInfo 對象。 |
特性能夠應用於泛型類型中,方式與應用於非泛型類型相同。 有關應用特性的更多信息,請參見 特性(C# 和 Visual Basic)。
自定義特性只容許引用開放泛型類型(未提供類型參數的泛型類型)和封閉構造泛型類型(爲全部類型參數提供參數)。
下面的示例使用此自定義特性:
class CustomAttribute : System.Attribute
{
public System.Object info;
}
特性能夠引用開放式泛型類型:
public class GenericClass1<T> { }
[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }
使用數目適當的若干個逗號指定多個類型參數。 在此示例中,GenericClass2 有兩個類型參數:
public class GenericClass2<T, U> { }
[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }
特性能夠引用封閉式構造泛型類型:
public class GenericClass3<T, U, V> { }
[CustomAttribute(info = typeof(GenericClass3<int, double, string>))]
class ClassC { }
引用泛型類型參數的特性將致使編譯時錯誤:
//[CustomAttribute(info = typeof(GenericClass3<int, T, string>))] //Error
class ClassD<T> { }
不能從 Attribute 繼承泛型類型:
//public class CustomAtt<T> : System.Attribute {} //Error
若要在運行時得到有關泛型類型或類型參數的信息,可使用 System.Reflection 的方法。 有關更多信息,請參見泛型和反射(C# 編程指南)