當(dāng)“設(shè)計(jì)模式”出現(xiàn)時(shí),人們提“用接口編程”;后來,有了泛型,人們提“用泛型編程”。什么泛型?比如,單鏈表 LinkedList 場景,每個(gè)節(jié)點(diǎn)包含兩個(gè)字段:值和下一個(gè)節(jié)點(diǎn)的引用,其中,“值”既可以是 int,也可以是 string,甚至是對象,為每個(gè)數(shù)據(jù)類型都寫一個(gè)類,顯然太麻煩,此時(shí)就可以使用泛型 LinkedList <T>,T 表示 int 或 string 類型等等;再如,排序算法中很常見 Swap(ref int a, ref int b) 函數(shù),可以交換兩個(gè) int 類型,當(dāng)然也可以是 string,用泛型也很合適。用 T 代表 int 和 string,甚至任何類型。
但問題是,實(shí)際項(xiàng)目中用 T 表示任何類型,顯然太粗放。比如,要是用 T 表示動(dòng)物和植物,動(dòng)物和植物可能是接口或基類,顯然動(dòng)物和植物不同,頂多都繼承生物基類或接口,我們倒是希望把 T 限定在動(dòng)物或植物,這樣在定義相應(yīng)的泛型類中就可以使用動(dòng)物或植物的成員——這就是泛型約束。
這就完美了~
所以,實(shí)際項(xiàng)目中T 往往不是任何類型,而是代表某個(gè)類型、某個(gè)基類、某個(gè)接口,說是任何類型,只是泛型表達(dá)自己的理念而已。
如果把 T 限定在某個(gè)基類、某個(gè)接口上,那么泛型類中就可以使用那個(gè)基類或接口中的成員。
如果要檢查泛型列表中的某個(gè)項(xiàng)以確定它是否有效,或者將它與其他某個(gè)項(xiàng)進(jìn)行比較,則編譯器必須在一定程度上保證它需要調(diào)用的運(yùn)算符或方法將受到客戶端代碼可能指定的任何類型參數(shù)的支持。 這種保證是通過對泛型類定義應(yīng)用一個(gè)或多個(gè)約束獲得的。
例如,基類約束告訴編譯器:僅此類型的對象或從此類型派生的對象才可用作類型參數(shù)。 一旦編譯器有了這個(gè)保證,它就能夠允許在泛型類中調(diào)用該類型的方法。約束是使用關(guān)鍵字 where 。
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; } }
}
/// <summary> /// 員工單鏈表 /// </summary> /// <typeparam name="T"></typeparam> public class EmployeeList<T> where T : Employee
{ /// <summary> /// Employee 節(jié)點(diǎn) /// </summary> 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 EmployeeList() { 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; }
}
“where T : Employee”約束使泛型類可以使用 Employee.Name 屬性,即 current.Data.Name。
類型為 T 的所有項(xiàng),都保證是 Employee 對象或從 Employee 繼承的對象。
編譯器除了假設(shè)類型參數(shù)派生自 System.Object 以外,不會(huì)做其他任何假設(shè)。在希望強(qiáng)制兩個(gè)類型參數(shù)之間的繼承關(guān)系的情況下,可對泛型類使用參數(shù)類型約束。
在定義泛型類時(shí),可以對客戶端代碼能夠在實(shí)例化類時(shí)用于類型參數(shù)的類型種類施加限制。 如果客戶端代碼嘗試使用某個(gè)約束所不允許的類型來實(shí)例化類,則會(huì)產(chǎn)生編譯時(shí)錯(cuò)誤。 這些限制稱為約束。 約束是使用 where 關(guān)鍵字。下表列出了六種類型的約束:
| 約束 | 說明 |
| T:結(jié)構(gòu) | 類型參數(shù)必須是值類型。 |
| T:類 | 類型參數(shù)必須是引用類型;這一點(diǎn)也適用于任何類、接口、委托或數(shù)組類型。 |
| T:new() | 類型參數(shù)必須具有無參數(shù)的公共構(gòu)造函數(shù)。 當(dāng)與其他約束一起使用時(shí),new() 約束必須最后指定。 |
| T:<基類名> | 類型參數(shù)必須是指定的基類或派生自指定的基類。 |
| T:<接口名稱> | 類型參數(shù)必須是指定的接口或?qū)崿F(xiàn)指定的接口。 可以指定多個(gè)接口約束。 約束接口也可以是泛型的。 |
| T:U | 為 T 提供的類型參數(shù)必須是為 U 提供的參數(shù)或派生自為 U 提供的參數(shù)。 |
可以對同一類型參數(shù)應(yīng)用多個(gè)約束,而且約束自身可以是泛型類型,如下所示:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{ // ... }
這樣就可以增加約束類型及其繼承層次結(jié)構(gòu)中的所有類型所支持的允許操作和方法。 因此,在設(shè)計(jì)泛型類或方法時(shí),如果要對泛型成員執(zhí)行除簡單賦值之外的任何操作或調(diào)用 System.Object 不支持的任何方法,您將需要對該類型參數(shù)應(yīng)用約束。
在應(yīng)用 where T : class 約束時(shí),避免對類型參數(shù)使用 == 和 != 運(yùn)算符,因?yàn)檫@些運(yùn)算符僅測試引用是否相等,而不不是值是否相等。即使在用作參數(shù)的類型中重載這些運(yùn)算符也是如此。下面代碼說明了這一點(diǎn):即使 String 類重載 == 運(yùn)算符,輸出也為 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); }
因?yàn)榫幾g器在編譯時(shí)僅知道 T 是引用類型,因此必須使用對所有引用類型都有效的默認(rèn)運(yùn)算符。這就好像對 int 類型和 string 類型的比較,顯然不同。
如果必須測試值是否相等,那么可以使用 where T : IComparable<T> 約束,并在泛型類中實(shí)現(xiàn)該接口。
沒有約束的類型參數(shù)(如公共類 SampleClass<T>{} 中的 T)稱為未綁定的類型參數(shù)。 未綁定的類型參數(shù)具有以下規(guī)則:
泛型類有泛型類型參數(shù),泛型類的成員函數(shù)也有自己的泛型參數(shù),但成員函數(shù)的泛型參數(shù)要約束在泛型類型參數(shù)上,此時(shí)就很用,如下示例所示:
class List<T> { void Add<U>(List<U> items) where U : T {/*...*/}
}
上面示例中,泛型類型參數(shù) T 在其成員函數(shù) Add 方法中有一個(gè)類型約束 where U : T,其中,Add 方法中使用了泛型 U,而在 List 類中并沒有綁定的類型參數(shù),沒有約束。
類型參數(shù)還可在泛型類定義中用作約束。注意,必須在尖括號(hào)中聲明此類型參數(shù)與任何其他類型的參數(shù):
//Type parameter V is used as a type constraint. public class SampleClass<T, U, V> where T : V { }
下載 Demo
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注