約束說明
定義泛型類時,可以對客戶端代碼能夠在實(shí)例化類時用于類型參數(shù)的幾種類型施加限制温眉。 如果客戶端代碼嘗試使用約束所不允許的類型來實(shí)例化類缸匪,則會產(chǎn)生編譯時錯誤。 這些限制稱為約束类溢。 通過使用 where 上下文關(guān)鍵字指定約束凌蔬。 下表列出了六種類型的約束:
約束 | 描述 |
---|---|
where T:結(jié)構(gòu) | 類型參數(shù)必須是值類型。 可以指定除 Nullable 以外的任何值類型闯冷。 有關(guān)詳細(xì)信息砂心,請參閱使用可以為 null 的類型。 |
where T:類 | 類型參數(shù)必須是引用類型蛇耀;這同樣適用于所有類辩诞、接口、委托或數(shù)組類型纺涤。 |
where T:new() | 類型參數(shù)必須具有公共無參數(shù)構(gòu)造函數(shù)译暂。 與其他約束一起使用時,new() 約束必須最后指定撩炊。 |
where T:<基類名稱> | 類型參數(shù)必須是指定的基類或派生自指定的基類外永。 |
where T:<接口名稱> | 類型參數(shù)必須是指定的接口或?qū)崿F(xiàn)指定的接口。 可指定多個接口約束拧咳。 約束接口也可以是泛型伯顶。 |
where T:U | 為 T 提供的類型參數(shù)必須是為 U 提供的參數(shù)或派生自為 U 提供的參數(shù)。 |
使用約束的原因
如果要檢查泛型列表中的某個項(xiàng)骆膝,確定它是否有效祭衩,或者將它與其他某個項(xiàng)進(jìn)行比較,則編譯器必須保證它需要調(diào)用的運(yùn)算符或方法將受到客戶端代碼可能指定的任何類型參數(shù)的支持阅签。 通過對泛型類定義應(yīng)用一個或多個約束獲得這種保證掐暮。 例如,基類約束告訴編譯器愉择,僅此類型的對象或派生自此類型的對象可用作類型參數(shù)劫乱。 編譯器有了此保證后织中,就能夠允許在泛型類中調(diào)用該類型的方法锥涕。 通過使用 where 上下文關(guān)鍵字應(yīng)用約束衷戈。 以下代碼示例演示可通過應(yīng)用基類約束添加到(泛型介紹中的)GenericList<T>
類的功能。
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 屬性层坠,因?yàn)轭愋?T 的所有項(xiàng)都保證是 Employee 對象或是從 Employee 繼承的對象殖妇。
可以對同一類型參數(shù)應(yīng)用多個約束,并且約束自身可以是泛型類型破花,如下所示:
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);
}
出現(xiàn)這種情況是因?yàn)榍ぃ幾g器在編譯時僅知道 T 是引用類型,因此必須使用對所有引用類型都有效的默認(rèn)運(yùn)算符座每。 如果必須測試值相等性前鹅,建議的方法是同時應(yīng)用where T : IComparable<T>
約束,并在將用于構(gòu)造泛型類的任何類中實(shí)現(xiàn)該接口峭梳。
約束多個參數(shù)
可以對多個參數(shù)應(yīng)用多個約束舰绘,對一個參數(shù)應(yīng)用多個約束,如下例所示:
class Base
{
}
class Test<T, U> where U : struct where T : Base, new()
{
}
未綁定的類型參數(shù)
沒有約束的類型參數(shù)(如公共類 SampleClass<T>{} 中的 T)稱為未綁定的類型參數(shù)葱椭。 未綁定的類型參數(shù)具有以下規(guī)則:
- 不能使用 != 和 == 運(yùn)算符捂寿,因?yàn)闊o法保證具體的類型參數(shù)能支持這些運(yùn)算符。
- 可以在它們與 System.Object 之間來回轉(zhuǎn)換孵运,或?qū)⑺鼈冿@式轉(zhuǎn)換為任何接口類型秦陋。
- 可以將它們與 null 進(jìn)行比較。 將未綁定的參數(shù)與 null 進(jìn)行比較時治笨,如果類型參數(shù)為值類型驳概,則該比較將始終返回 false。
類型參數(shù)作為約束
在具有自己類型參數(shù)的成員函數(shù)必須將該參數(shù)約束為包含類型的類型參數(shù)時旷赖,將泛型類型參數(shù)用作約束非常有用顺又,如下例所示:
class List<T>
{
void Add<U>(List<U> items) where U : T
{
/*...*/
}
}
在上述示例中,T 在 Add 方法的上下文中是一個類型約束杠愧,而在 List 類的上下文中是一個未綁定的類型參待榔。
類型參數(shù)還可在泛型類定義中用作約束。 請注意流济,必須在尖括號中聲明此類型參數(shù)以及任何其他類型參數(shù):
//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V
{
}
類型參數(shù)作為泛型類的約束的作用非常有限锐锣,因?yàn)榫幾g器除了假設(shè)類型參數(shù)派生自 System.Object 以外,不會做其他任何假設(shè)绳瘟。 如果要在兩個類型參數(shù)之間強(qiáng)制繼承關(guān)系雕憔,可以將類型參數(shù)用作泛型類的約束。