在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。 如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 约束是使用 where 上下文关键字指定的。 下表列出了六种类型的约束:
约束 | 说明 |
---|---|
T:结构 | 类型参数必须是值类型。 可以指定除 Nullable 以外的任何值类型。 有关更多信息,请参见使用可以为 null 的类型(C# 编程指南)。 |
T:类 | 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 |
T:new() | 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。 |
T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。 |
T:U | 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 |
如果要检查泛型列表中的某个项以确定它是否有效,或者将它与其他某个项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。 这种保证是通过对泛型类定义应用一个或多个约束获得的。 例如,基类约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。 一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。 约束是使用上下文关键字 where 应用的。 下面的代码示例演示可通过应用基类约束添加到 GenericList<T> 类(在泛型介绍(C# 编程指南)中)的功能。
下面是对数组的基类进行扩展,扩展的方法是一个泛型方法 DataBox 是一个泛型,而 where 关键字约束了这个DataBox 泛型只能是DataBoxBase类型或他的子类型,并且之后的约束中告诉编译器可以使用new()方法构建这个泛型对象
using System;
namespace MpgNetPublicDll
{
/// <summary>
/// 功 能:
/// 提供扩展方法的类
/// 描 述:
/// 自定认的扩展方法(这是很好用的东西,学习中)
///
/// 作 者:
/// 黄波
/// 创建时间:
/// 2014/01/17
/// 版权所有:
/// Copyright (C) MPG
/// 修改记录:
/// </summary>
public static class ExtendMethod
{
/// <summary>
/// 深层复制DataBox
/// </summary>
/// <typeparam name="DataBox">DataBoxBase的所有子类型</typeparam>
/// <param name="dbs">原对象</param>
/// <param name="newObjs">生成的新的对象</param>
public static void CopyTo<DataBox>(this Array dbs, DataBox[] newObjs) where DataBox : DataBoxBase, new() //泛型约束
{
for (int i = 0; i < dbs.Length; i++)
{
if (dbs.GetValue(i) is DataBoxBase)
{
newObjs[i] = new DataBox();
DataBox old = (DataBox)dbs.GetValue(i);
for (int b = 0; b < old.Count; b++)
{
newObjs[i][b] = old[b];
}
}
}
}
}
}
再看一个简单一点的经典的例子:
public class AAA
{
public string MyPropertyA { get; set; }
public virtual T copy<T>() where T :AAA, new() //泛型约束。约束中不能给定多个类
{
T t = new T();
t.MyPropertyA = "基类的属性A";
return t;
}
}
public class BBB :AAA
{
public string MyPropertyB { get; set; }
public override T copy<T>()
{
BBB ba2 = base.copy<BBB>(); //最重要的调用是这个地方,一定要在copy<BBB>中给定BBB(BBB派生自AAA),或指定AAA以及其它派生自AAA的子类。因为泛型约束指定了这里可以使用的类型,
string tmp = ba2.MyPropertyA; //这里可以看到MyPropertyA属性的值已经是"基类的属性A"。因为它调用了父类的base.copy<T>();方法
ba2.MyPropertyB = "子类的属性B"; //这里放入BBB的属性MyPropertyB的值,因为父类中不可能处理这个值。
BBB ba = (BBB)base.copy<AAA>(); //当然这样也是可以的!只是要强转一下。
tmp = ba2.MyPropertyA;
ba.MyPropertyB = "子类的属性B";
return base.copy<T>();
}
}