CLR via C# 泛型 约束
编译泛型代码时,C#编译器会进行分析,确保代码适用于当前已有或将来可能定义的任何类型。
这个方法适用于任何类型。无论T是引用类型,是值类型或枚举类型,还是接口或委托类型,它都能工作。这个方法适用于当前存在的所有类型,也适用于将来可能定义的任何类型,因为所有类型都支持对object类型的变量的赋值,也支持对object类型定义的方法的调用(比如ToString和Equals)。
看下面这个方法:
Min方法试图使用o1变量类调用CompareTo方法。但是,许多类型都没有提供CompareTo方法,所以C#编译器不能编译上述代码,它不能保证这个方法适用于所有类型。
幸好,编译器和CLR支持称为约束的机制,可通过它使泛型变得真正有用。
约束的作用是限制能指定泛型实参的类型数量。通过限制类型的数量,可以对那些类型执行更多的操作,以下是Min方法的新版本,它指定了一个约束:
C#的where 关键字告诉编译器,为T指定的任何类型都必须实现同类型(T)的泛型IComparable接口。有了这个约束,就可以在方法中调用CompareTo,因为已知IComparable<T>接口定义了CompareTo。
//--
约束可应用于泛型类型的类型参数,也可应用于泛型方法的类型参数。CLR不允许基于类型参数名称或约束类进行重载;只能基于元数(类型参数个数)对类型或方法进行重载。
//--
//--
重写虚方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数会继承在基类型方法上指定的约束。事实上,根本不允许为重写方法的类型参数指定任何约束。但类型参数的名称是可以改变的。类似的,实现接口方法时,方法必须指定与接口方法等量的类型参数,这些类型参数将继承由接口方法指定的约束。
从Derived类的M<T3,T4>方法中移除两个where子句,代码就能正常编译了。类型参数的名称可以更改,比如T1改成T3;但约束不能更改(甚至不能指定)。
//--主要约束
类型参数可以指定零个或者一个主要约束。
主要约束可以是代表非密封类的一个引用类型。不能指定以下特殊引用类型:System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum或者System.Void。
指定引用类型约束时,相当于向编译器承诺:一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型。
在这个类定义中,类型参数T设置了主要约束Stream。这就告诉编译器,使用PrimaryConstraintOfStream的代码在指定类型实参时,必须指定Stream或者从Stream派生的类型。如果类型参数没有指定主要约束,就默认为System.Object。
有两个特殊的主要约束:class 和 struct 。其中,class约束向编译器承诺类型实参是引用类型。任何类类型、接口类型、委托类型或者数组类型都满足这个约束。
在这个例子中,将temp设为null是合法的,因为T已知是引用类型,而所有引用类型的变量都能设为null。不对T进行约束的话,上述代码就通不过编译,因为T可能是值类型,而值类型的 变量不能设为null。
struct约束向编译器承诺类型实参是值类型。包括枚举在内的任何值类型都满足这个约束。但编译器和CLR将任何System.Nullable<T>值类型视为特殊类型,不满足这个struct约束。原因是Nullable<T>类型将它的类型参数约束为struct,而CLR希望禁止向Nullable<Nullable<T>>这样的递归类型。
例子中new T()是合法的,因为T已知是值类型,而所有值类型都隐式的有一个公共无参构造器。如果T不约束,约束为引用类型,或者约束为class,上述代码将无法通过编译,因为有的引用类型没有公共无参构造器。
//--次要约束
类型参数可以指定零个或者多个次要约束,次要约束代表接口类型。这种约束向编译器承诺类型实参实现了接口。由于能指定多个接口约束,所以类型实参必须实现了所有接口约束(以及主要约束,如果有的话)。
还有一种次要约束称为类型参数约束,有时也称为裸类型约束。这种约束用的比接口约束少得多。它允许一个泛型类型或方法规定:指定的类型实参要么就是约束的类型,要么是约束的类型的派生类。一个类型参数可以指定零个或者多个类型参数约束。
//--构造器约束
类型参数可指定零个或一个构造器约束,它向编译器承诺类型实参是实现了公共无参构造器的非抽象类型。注意,如果同时使用构造器约束和struct约束,C#编译器会认为这是一个错误,因为这是多余的;所有值类型都隐式提供了公共无参构造器。
目前,CLR(以及C#编译器)只支持无参构造器。