定義泛型類時,能夠對客戶端代碼可以在實例化類時用於類型參數的幾種類型施加限制。 若是客戶端代碼嘗試使用約束所不容許的類型來實例化類,則會產生編譯時錯誤。 這些限制稱爲約束。 經過使用 where 上下文關鍵字指定約束。 下表列出了六種類型的約束: 約束 描述 where T:結構 類型參數必須是值類型。 能夠指定除 Nullable 之外的任何值類型。 有關詳細信息,請參閱使用能夠爲 null 的類型。 where T:類 類型參數必須是引用類型;這一樣適用於全部類、接口、委託或數組類型。 where T:new() 類型參數必須具備公共無參數構造函數。 與其餘約束一塊兒使用時,new() 約束必須最後指定。 where T:<基類名稱> 類型參數必須是指定的基類或派生自指定的基類。 where T:<接口名稱> 類型參數必須是指定的接口或實現指定的接口。 可指定多個接口約束。 約束接口也能夠是泛型。 where 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 繼承的對象。 能夠對同一類型參數應用多個約束,而且約束自身能夠是泛型類型,以下所示: C#ui
複製 class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new() { // ... } 經過約束類型參數,能夠增長約束類型及其繼承層次結構中的全部類型所支持的容許操做和方法調用的數量。 所以,在設計泛型類或方法時,若是要對泛型成員執行除簡單賦值以外的任何操做或調用 System.Object 不支持的任何方法,則必須對該類型參數應用約束。 在應用 where T : class 約束時,請避免對類型參數使用 == 和 != 運算符,由於這些運算符僅測試引用標識而不測試值相等性。 即便在用做參數的類型中重載這些運算符也是如此。 下面的代碼說明了這一點;即便 String 類重載 == 運算符,輸出也爲 false。 C#設計
複製 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> 約束,並在將用於構造泛型類的任何類中實現該接口。 約束多個參數 能夠對多個參數應用多個約束,對一個參數應用多個約束,以下例所示: C#code
複製 class Base { } class Test<T, U> where U : struct where T : Base, new() { } 未綁定的類型參數 沒有約束的類型參數(如公共類 SampleClass<T>{} 中的 T)稱爲未綁定的類型參數。 未綁定的類型參數具備如下規則: 不能使用 != 和 == 運算符,由於沒法保證具體的類型參數能支持這些運算符。 能夠在它們與 System.Object 之間來回轉換,或將它們顯式轉換爲任何接口類型。 能夠將它們與 null 進行比較。 將未綁定的參數與 null 進行比較時,若是類型參數爲值類型,則該比較將始終返回 false。 類型參數做爲約束 在具備本身類型參數的成員函數必須將該參數約束爲包含類型的類型參數時,將泛型類型參數用做約束很是有用,以下例所示: C#對象
複製 class List<T> { void Add<U>(List<U> items) where U : T {/.../} } 在上述示例中,T 在 Add 方法的上下文中是一個類型約束,而在 List 類的上下文中是一個未綁定的類型參數。 類型參數還可在泛型類定義中用做約束。 請注意,必須在尖括號中聲明此類型參數以及任何其餘類型參數: C#繼承
複製 //Type parameter V is used as a type constraint. public class SampleClass<T, U, V> where T : V { } 類型參數做爲泛型類的約束的做用很是有限,由於編譯器除了假設類型參數派生自 System.Object 之外,不會作其餘任何假設。 若是要在兩個類型參數之間強制繼承關係,能夠將類型參數用做泛型類的約束。 另請參閱 System.Collections.Generic C# 編程指南 泛型介紹 泛型類 new 約束接口