C# 中使用泛型约束来限制类型参数范围

在定义泛型类型时,可以指定一个或者多个约束来限制泛型参数的范围。在实例化一个泛型类型时,若提供的类型不满足约束要求,编译器就会报错。

new() 约束

new 约束要求类型参数必须有公共无参数构造函数,与其它约束一起使用时,new() 约束必须放在最后。因为值类型都具有默认的公共无参构造函数,所以所有的值类型都满足 new() 约束。

 class DataStore where T : new()
 {
     public T Data { get; set; }
 }
 // int 是值类型,满足约束
 var store = new DataStore();

struct 约束

struct 约束要求类型参数只能是非空值类型,struct 类型不能和 new() 同时使用,原因前面已经说过,即所有的值类型自动满足 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 约束要求类型参数只能是引用类型,比如类、接口、委托、数组。在 C# 8.0 即以后,若打开了 nullable 开关,则必须是不可空引用类型。

 class DataStore where T : class
 {
     public T Data { get; set; } = default!;
 }
 
 // 报错,int 不是引用类型
 var store = new DataStore();
 
 // 在C#8.0以上打开 nullable 开关后报错
 // 因为是可空的
 var store = new DataStore();
 
 // 正确,string 是不可空引用类型
 var store = new DataStore();

class? 约束和 class 类似,都要求类型参数必须是引用类型,但对是否是 nullable 没有要求。

 class DataStore where T : class?
 {
     public T Data { get; set; }
 }
 
 // 正确,无论可空或者不可空的引用类型都可以
 var store = new DataStore();
 
 // 正确,无论可空或者不可空的引用类型都可以
 var store = new DataStore();

基类约束和接口约束

BaseClass 约束要求类型参数必须是指定的基类或者派生自指定的基类。在 C# 8.0 即以后,若打开了 nullable 开关,则必须是不可空的,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();

此外,还有类似的 InterfaceInterface? 约束,即类型参数必须实现指定的接口。在 C# 8.0 及以后版本,若打开了 nullable 开关,类型参数必须不可空,Interface? 则不做要求

 class DataStore where T : IComparable
 {
     public T Data { get; set; } = default!;
 }

类型参数作为约束

where T : U 约束要求类型参数 T 必须是 U 或者 U的子类型。开启了 nullable 开关后,两者的可空性必须相同。

 public class List
 {
     public void Add(List items) where U : T
 }

notnull约束

C# 8.0 引入了 notnull 约束, 要求类型参数必须非空,既可以是非空值类型,也可以是非空引用类型。

 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 作为基类约束,用于限制类型参数为枚举类型。

 public static IEnumerable<(string Label, int Value)> GetEnumChoices() where T : System.Enum
 {
     var values = Enum.GetValues(typeof(T));
     var list = new List<(string Label, int Value)>();
     foreach (int value in values)
         list.Add((Enum.GetName(typeof(T), value)!, value));
     return list;
 }

以上代码定义了一个从枚举获取值-名称对通用方法,Enum 约束让代码可以安全调用 Enum.GetValues 方法,无需额外的代码来判断类型是不是枚举。

委托约束

委托约束和枚举约束一样,也是一种特殊类型的基类约束,用于限定类型参数必须为委托。

 public static TDelegate? TypeSafeCombine(this TDelegate source, TDelegate target)
     where TDelegate : System.Delegate
     => Delegate.Combine(source, target) as TDelegate;

通过使用 System.Delegate 约束,上述代码可以安全地调用 Delegate.Combine 来合并两个委托。否则就需要额外的代码来判断类型是不是委托。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章