在定義泛型類(lèi)型時(shí),可以指定一個(gè)或者多個(gè)約束來(lái)限制泛型參數(shù)的范圍。在實(shí)例化一個(gè)泛型類(lèi)型時(shí),若提供的類(lèi)型不滿(mǎn)足約束要求,編譯器就會(huì)報(bào)錯(cuò)。
new() 約束
new 約束要求類(lèi)型參數(shù)必須有公共無(wú)參數(shù)構(gòu)造函數(shù),與其它約束一起使用時(shí),new() 約束必須放在最后。因?yàn)橹殿?lèi)型都具有默認(rèn)的公共無(wú)參構(gòu)造函數(shù),所以所有的值類(lèi)型都滿(mǎn)足 new() 約束。
class DataStore where T : new() { public T Data { get; set; } } // int 是值類(lèi)型,滿(mǎn)足約束 var store = new DataStore();
struct 約束
struct 約束要求類(lèi)型參數(shù)只能是非空值類(lèi)型,struct 類(lèi)型不能和 new() 同時(shí)使用,原因前面已經(jīng)說(shuō)過(guò),即所有的值類(lèi)型自動(dòng)滿(mǎn)足 new() 約束。
class DataStore where T : struct { public T Data { get; set; } } // 報(bào)錯(cuò),因?yàn)?string 是引用類(lèi)型 var store = new DataStore(); // 報(bào)錯(cuò),因?yàn)槭强煽罩殿?lèi)型 var store = new DataStore(); //正確,int 是值類(lèi)型 var store = new DataStore();
class 和 class?約束
class 約束要求類(lèi)型參數(shù)只能是引用類(lèi)型,比如類(lèi)、接口、委托、數(shù)組。在 C# 8.0 即以后,若打開(kāi)了 nullable 開(kāi)關(guān),則必須是不可空引用類(lèi)型。
class DataStore where T : class { public T Data { get; set; } = default!; } // 報(bào)錯(cuò),int 不是引用類(lèi)型 var store = new DataStore(); // 在C#8.0以上打開(kāi) nullable 開(kāi)關(guān)后報(bào)錯(cuò) // 因?yàn)槭强煽盏?var store = new DataStore(); // 正確,string 是不可空引用類(lèi)型 var store = new DataStore();
class? 約束和 class 類(lèi)似,都要求類(lèi)型參數(shù)必須是引用類(lèi)型,但對(duì)是否是 nullable 沒(méi)有要求。
class DataStore where T : class? { public T Data { get; set; } } // 正確,無(wú)論可空或者不可空的引用類(lèi)型都可以 var store = new DataStore(); // 正確,無(wú)論可空或者不可空的引用類(lèi)型都可以 var store = new DataStore();
基類(lèi)約束和接口約束
BaseClass 約束要求類(lèi)型參數(shù)必須是指定的基類(lèi)或者派生自指定的基類(lèi)。在 C# 8.0 即以后,若打開(kāi)了 nullable 開(kāi)關(guān),則必須是不可空的,BaseClass? 則對(duì)可空性不做要求,既可以為不可空的,也可以為可空的。
public class Person { public string Name { get; set; } = string.Empty; } public class Student : Person { public int Score { get; set; } } class DataStore where T : Person { public T Data { get; set; } = default!; } // 錯(cuò)誤,Person? 為可空類(lèi)型 // 如需適用可空類(lèi)型,使用 where T : Person? 約束 var store = new DataStore(); // 正確,Student 派生自 Person var store = new DataStore();
此外,還有類(lèi)似的 Interface 和 Interface? 約束,即類(lèi)型參數(shù)必須實(shí)現(xiàn)指定的接口。在 C# 8.0 及以后版本,若打開(kāi)了 nullable 開(kāi)關(guān),類(lèi)型參數(shù)必須不可空,Interface? 則不做要求
class DataStore where T : IComparable { public T Data { get; set; } = default!; }
類(lèi)型參數(shù)作為約束
where T : U 約束要求類(lèi)型參數(shù) T 必須是 U 或者 U的子類(lèi)型。開(kāi)啟了 nullable 開(kāi)關(guān)后,兩者的可空性必須相同。
public class List { public void Add(List items) where U : T }
notnull約束
C# 8.0 引入了 notnull 約束, 要求類(lèi)型參數(shù)必須非空,既可以是非空值類(lèi)型,也可以是非空引用類(lèi)型。
class DataStore where T : notnull { public T Data { get; set; } = default!; } // 錯(cuò)誤,int? 為可空值類(lèi)型 var store = new DataStore(); // 錯(cuò)誤,Person? 為可空引用類(lèi)型 var store = new DataStore(); // 正確 var store = new DataStore(); // 正確 var store = new DataStore();
枚舉約束
從 C# 7.3 開(kāi)始,C# 允許使用 System.Enum 作為基類(lèi)約束,用于限制類(lèi)型參數(shù)為枚舉類(lèi)型。
public static IEnumerable GetEnumChoices() where T : System.Enum { var values = Enum.GetValues(typeof(T)); var list = new List(); foreach (int value in values) list.Add((Enum.GetName(typeof(T), value)!, value)); return list; }
以上代碼定義了一個(gè)從枚舉獲取值-名稱(chēng)對(duì)通用方法,Enum 約束讓代碼可以安全調(diào)用 Enum.GetValues 方法,無(wú)需額外的代碼來(lái)判斷類(lèi)型是不是枚舉。
委托約束
委托約束和枚舉約束一樣,也是一種特殊類(lèi)型的基類(lèi)約束,用于限定類(lèi)型參數(shù)必須為委托。
public static TDelegate? TypeSafeCombine(this TDelegate source, TDelegate target) where TDelegate : System.Delegate => Delegate.Combine(source, target) as TDelegate;
通過(guò)使用 System.Delegate 約束,上述代碼可以安全地調(diào)用 Delegate.Combine 來(lái)合并兩個(gè)委托。否則就需要額外的代碼來(lái)判斷類(lèi)型是不是委托。