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