在泛型類型或方法中,類型參數是客戶端在實例化泛型類型的變量時,指定的特定類型的占位符。
若要使用GenericList<T>,客戶端代碼必須通過指定尖括號中的類型參數來聲明和實例化構造類型。此特定類的類型參數可以是編譯器識別的任何類型。可以創建任意數目的構造類型實例,每個實例使用不同的類型參數。
MyGenericList<float> list2 = new MyGenericList<float>(); MyGenericList<int> list3 = new MyGenericList<int>();
每個MyGenericList<T>實例中,類中出現每個T都會在運行時替代為相應的類型參數。通過替代這種方式,我們使用一個類定義創建了2個獨立的類型安全的有效對象。
類型參數命名准則
務必使用描述性名稱命名泛型類型參數,除非單個字母名稱完全可以讓人了解它表示的含義,而描述性名稱不會有更多的意義。
public interface ISessionChannel<TSession> { } public delegate TOutput Convert<TInput, TOutput>(TInput from); public class List<T> { }
考慮使用T作為具有單個字母類型參數的類型的類型參數名
public delegate boolPredicate<T>(T item);
務必將T作為描述性類型參數名的前綴
public interface ISessionChannel<TSession> { TSession Session { get; } }
考慮在參數名中指示對此類型參數的約束。例如,可以將帶有ISession約束的參數命名為TSession。
類型參數的約束
在定義泛型類時,可以對客戶端代碼能夠在實例化類時用於類型參數的類型種類施加限制。如果客戶端代碼嘗試使用某個約束所不允許的類型來實例化類,則會產生編譯時錯誤。這些限制稱為約束。約束是使用Where上下問關鍵字指定的。
約束 | 說明 |
T:結構 | 類型參數必須是值類型。可以指定除Nullable以外的任何值類型。 |
T:類 | 類型參數必須是引用類型,包括任何類、接口、委托或者數組類型。 |
T:new() | 類型參數必須具有無參書的公共構造函數。當與其他約束一起使用時,new()約束必須最后指定。 |
T:<基類名> | 類型參數必須是指定的基類或者派生指定的基類。 |
T:<接口名稱> | 類型參數必須是指定的接口或者實現指定的接口。可以指定多個接口約束。約束接口也可以是泛型的。 |
T:U | 為T提供的類型參數必須是U提供的參數或者派生自為U提供的參數。這稱為裸類型約束 |
使用約束的原因
如果要檢查泛型列表中的某項以確認它是否有效,或者將它與其他某項進行比較,則編譯器必須在一定程度上保證它需要調用的運算符或方法將受到客戶端代碼可能指定的任何類型參數的支持。這種保證是通過對泛型類定義應該一個或多個約束約束獲得的。例如,基類約束告訴編譯器:僅此類型的對象或從此類型派生的對象才可用作類型參數。一旦編譯器有了這個保證,它就能夠允許在泛型類中調用該類型的方法。約束是使用上下問關鍵字where應用的。
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() { 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) { // 約束使得泛型類能夠使用Employee.Name屬性 //因為類型為T的所有項都保證是Employee對象或從Employee繼承的對象。 // the constraint enables access to the Name property if (current.Data.Name == s) { t = current.Data; break; } else { current = current.Next; } } return t; } }
可以對同一類型參數應用多個約束,並且約束自身可以是泛型類型
class EmployeeList<T> where T : Employee, System.IComparable<T>, new() { }
通過約束類型參數,可以增加約束類型及其繼承層次結構中的所有類型所支持的允許操作和方法調用的數量。因此,在設計泛型類或方法時,如果要對泛型成員執行除簡單賦值之外的任何操作或者調用System.Object不支持的任何方法,您將需要對該類型應用約束。
在應該應用where T:class 約束時,建議不要對類型參數使用== 和!=運算符,因為這些運算符測試引用同一性而不測試值相等。(==和!=運算符比較的是引用,而Equals比較的是值)即使在用作參數的類型中重載這些運算符也是如此。
static void Main(string[] args) { string s1 = "foo"; System.Text.StringBuilder sb = new System.Text.StringBuilder("foo"); string s2 = sb.ToString(); Optest<string>(s1, s2); System.Console.ReadKey(); } public static void Optest<T>(T s, T t) where T : class { System.Console.WriteLine(s == t);// false System.Console.WriteLine(s.Equals(t));//true System.Console.WriteLine("foo equal foo? {0}", ("foo" == "foo"));//true }
對於此種情況,如果需要測試值相等性,建議的方法時同時應用where T:IComparable<T>約束,並在將用於構造泛型類的任何類中實現該接口。
未綁定的類型參數
沒有約束的類型參數(如公共類SampleClass<T>{}中的T)稱為未綁定的類型參數。未綁定的類型參數具有以下規則:
- 不能使用!=和==運算符,因為無法保證具體類型參數能支持這些運算符。
可以在它們與System.Object之間回來轉換,或將它們顯式轉換為任何接口類型。
可以將它們與null進行比較。將未綁定的參數與null進行比較時,如果類型參數為值類型,則該比較將始終返回false
裸類型約束
用作約束的泛型類型參數稱為裸類型約束。當具有自己的類型參數的成員函數需要將該參數約束為包括類型的類型參數時,裸類型約束很有用。