C# 泛型约束

C# 允许您使用约束来限制客户端代码在实例化泛型类型时指定某些类型。 如果您尝试使用指定约束不允许的类型来实例化泛型类型,则会出现编译时错误。

您可以使用泛型类型名称后的 where 子句对泛型类型指定一个或多个约束。

GenericTypeName<T> where T  : contraint1, constraint2

以下示例演示了在实例化泛型类时具有引用类型约束的泛型类。

class ResultVm<T> where T : class
{
    public T Payload { get; set; }
}

上面,我们应用了类约束,这意味着在创建 ResultVm 类对象时,只能将引用类型作为参数传递。 因此,您可以传递引用类型,例如类、接口、委托或数组类型。 传递值类型会产生编译时错误,因此我们不能传递原始数据类型或结构类型。

var store = new ResultVm<string>(); // 合法的
var store = new ResultVm<MyClass>(); // 合法的
var store = new ResultVm<IMyInterface>(); // 合法的
var store = new ResultVm<IMyInterface>(); // 合法的
var store = new ResultVm<ArrayList>(); // 合法的
//ResultVm<int> store = new ResultVm<int>(); // 编译错误

在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。 下表列出了各种类型的约束: 下表列出了 类型参数的约束。

约束 说明
where T : struct 类型参数必须是不可为 null 的值类型。 有关可为 null 的值类型的信息,请参阅可为 null 的值类型。 由于所有值类型都具有可访问的无参数构造函数,因此 struct 约束表示 new() 约束,并且不能与 new() 约束结合使用。 struct 约束也不能与 unmanaged 约束结合使用。
where T : class 类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型。 在 C#8.0 或更高版本中的可为 null 上下文中,T 必须是不可为 null 的引用类型。
where T : class? 类型参数必须是可为 null 或不可为 null 的引用类型。 此约束还应用于任何类、接口、委托或数组类型。
where T : notnull 类型参数必须是不可为 null 的类型。 参数可以是 C# 8.0 或更高版本中的不可为 null 的引用类型,也可以是不可为 null 的值类型。
where T : default 重写方法或提供显式接口实现时,如果需要指定不受约束的类型参数,此约束可解决歧义。 default 约束表示基方法,但不包含 class 或 struct 约束。 有关详细信息,请参阅default约束规范建议。
where T : unmanaged 类型参数必须是不可为 null 的非托管类型。 unmanaged 约束表示 struct 约束,且不能与 struct 约束或 new() 约束结合使用。
where T : new() 类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。 new() 约束不能与 struct 和 unmanaged 约束结合使用。
where T : 类型参数必须是指定的基类或派生自指定的基类。 在 C# 8.0 及更高版本中的可为 null 上下文中,T 必须是从指定基类派生的不可为 null 的引用类型。
where T : ? 类型参数必须是指定的基类或派生自指定的基类。 在 C# 8.0 及更高版本中的可为 null 上下文中,T 可以是从指定基类派生的可为 null 或不可为 null 的类型。
where T : 类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在 C# 8.0 及更高版本中的可为 null 上下文中,T 必须是实现指定接口的不可为 null 的类型。
where T : ? 类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在 C# 8.0 中的可为 null 上下文中,T 可以是可为 null 的引用类型、不可为 null 的引用类型或值类型。 T 不能是可为 null 的值类型。
where T : U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 在可为 null 的上下文中,如果 U 是不可为 null 的引用类型,T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型,则 T 可以是可为 null 的引用类型,也可以是不可为 null 的引用类型。

where T : struct

以下示例演示了struct约束,这个类型参数只能 只能是非可空类型。

class ResultVm<T> where T : struct
{
    public T Data { get; set; }
}

ResultVm<int> store = new ResultVm<int>(); // 合法的
ResultVm<char> store = new ResultVm<char>(); // 合法的
ResultVm<MyStruct> store = new ResultVm<MyStruct>(); // 合法的
//ResultVm<string> store = new ResultVm<string>(); // 编译出错
//ResultVm<IMyInterface> store = new ResultVm<IMyInterface>(); // 编译出错
//ResultVm<ArrayList> store = new ResultVm<ArrayList>(); // 编译出错

where T : new()

以下示例演示了 new() 约束,这个类型参数 必须具有必须是具有公共的无参数构造函数的非抽象类型 (注意一个class或者struct如果没有构造函数。默认是有一个无参构造函数)

class ResultVm<T> where T : class, new()
{
    public T Data { get; set; }
}

ResultVm<MyClass> store = new ResultVm<MyClass>(); // 合法的
ResultVm<ArrayList> store = new ResultVm<ArrayList>(); // 合法的
//ResultVm<string> store = new ResultVm<string>(); // 编译出错 (因为string没有无参构造函数)
ResultVm<int> store = new ResultVm<int>(); // 合法的
//ResultVm<IMyInterface> store = new ResultVm<IMyInterface>(); // 编译出错 

where T : baseclass

以下示例演示了 继承 约束,这个类型参数 必须继承于我们的baseclass或者接口

class ResultVm<T> where T : IEnumerable
{
    public T Data { get; set; }
}

var vm1 = new ResultVm<ArrayList>(); // 合法的
var vm2 = new ResultVm<List<int>>(); // 合法的
var vm3 = new ResultVm<string>(); // 合法的 ,因为string有继承于IEnumerable
//var vm4 = new ResultVm<int>(); // 编译出错 
//var vm5 = new ResultVm<IFile>(); // 编译出错 因为我们自定义的IFile没有继承于IEnumerable

这是一个很有用的功能,因为只有一组行为相似的类,我们才好把它做到泛型里面。

两个泛型约束

class ResultVm<T1,T2> where T1 : class, new() where T2 : class, new()
{
    public T1 Data { get; set; }

    public T2 Data2 { get; set; }
}
下一篇:C# 集合
最近更新的
...