CLR via C#学习笔记-第十二章-可验证性和约束

12.8 可验证性和约束

where关键字

编译器和CLR支持称为约束的机制,可通过它使泛型变得真正有用。

约束的作用限制能指定成泛型实参的类型数量,通过限制类型的数量,可以对那些类型执行更多操作:

public static T Min<T>(T o1,T o2) where T:IComparable<T>{
    if(o1.ComparaTo(o2)<0) return o1;
    return o2;
}

C#的where关键字告诉编译器,为T制定的任何类型都必须实现同类型T的泛型IComparable接口。

有了这个约束就可以在方法中调用CompareTo,因为已知IComparable<T>接口定义了CompareTo.

现在当代码引用泛型类型或方法时,编译器要负责保证类型实参符合指定的约束,例如:

private static void CallMin(){
    Object o1="A",o2="B";
    Object oMin=Min<Object>(o1,o2);//Error CS0311  
}

编译器便会报错,以为内System.Object没有实现IComparable<Object>接口,事实上Object没有实现任何接口。

约束不能用于重载

约束可应用于泛型类型的类型参数,也可应用于泛型方法的类型参数。

CLR不允许基于类型参数名称或约束来进行重载:只能基于类型参数个数对类型或方法进行重载:

//可定义的类型
internal sealed class AType{}
internal sealed class AType<T>{}
internal sealed class AType<T1,T2>{}
//错误,与没有约束的AType<T>冲突
internal sealed class AType<T> where T:IComparable<T>{}
//错误,与AType<T1,T2>冲突
internal sealed class AType<T3,T4>{}

internal sealed class AnotherType{
    //可定义以下方法
    private static void M(){}
    private static void M<T>(){}
    private static void M<T1,T2>(){}
    //错误,与没有约束的M<T>冲突
    private static void M<T>() where T:IComparable<T>{}
    //错误,与M<T1,T2>冲突
    private static void M<T3,T4)(){}
}

虚方法的约束

重写虚泛型方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数会继承在基类方法上指定的约束。

事实上根本不允许为重写方法的类型参数指定任何约束,但类型参数的名称是可以改变的。

实现接口方法时方法必须指定与接口方法等量的类型参数,这些类型参数将继承由接口方法指定的约束:

internal class base{
    public virtual void M<T1,T2>() where T1:struct where T2:class{}
}
internal sealed class Derived:Base{
    public override void M<T3,T4>() where T3:EventArgs where T4:class{}//错误
}

编译以上代码,会报错:重写和显式接口实现方法的约束是从基方法继承的,因此不能直接指定这些约束。

从Derived类的M<T3,T4>方法中移除两个where子句,代码就能正常编译了。

注意,类型参数的名称可以更改,比如T1改成T3;但约束不能更改甚至不能指定。

12.8.1 主要约束

类型参数可以指定零个或者一个主要约束,主要约束可以是代表非密封类的一个引用类型。

不能约束以下特殊引用类型:System.Object、Array、Delegate、MulticastDelegate、ValueType、Enum、Void。

引用类型约束

指定引用类型约束时,相当于向编译器承诺:一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型:

internal sealed class PrimaryConstraintOfStream<T> where T:Stream{
    public void M(T stream){
        stream.Close();//正确
    }
}

类型参数T设置了主要约束Stream。这就告诉编译器使用PrimaryConstraintOfStream的代码在指定类型实参时,必须指定Stream或者从其中派生的类型比如FileStream。

如果类型参数没有指定主要约束,就默认为System.Object,但若果在源代码中显式指定Object,就会报错。

特殊的主要约束

有两个特殊的主要约束:struct和class。其中class约束向编译器承诺类型参数是引用类型。

任何类类型、接口类型、委托类型或者数组类型都满足这个约束:

internal sealed class PrimaryConstraintOfClass<T> where T:class{
    public void M(){
        T temp=null;//允许,因为T肯定是引用类型
    }
}

值类型约束

struct约束向编译器承诺类型实参是值类型。包括枚举在内的任何值类型都满足这个约束。

但编译器和CLR将任何System.Nullable<T>值类型视为特殊类型,不满足struct约束。

原因是Nullable<T>类型将它的类型参数约束为struct,而CLR希望禁止向Nullable<Nullable>>这样的递归类型。可空类型在第十九章讨论。

以下是示例使用struct约束他的类型参数:

internal sealed class PrimaryConstraintOfStruct<T> where T:struct{
    public void T Factory(){
        return new T();//允许,因为所有值类型都隐藏有一个公共无参构造器
    }
}

例子中的new T()是合法的,因为T已知是值类型,而所有的值类型都隐式有一个公共无参构造器。

如果是class,上述代码无法编译,因为有的引用类型没有公共无参构造器。

12.8.2 次要约束

接口约束

类型参数可以指定零个或多个次要约束,次要约束代表接口类型。这种约束向编译器承诺类型实参实现了接口。

由于能指定多个接口约束,所以类型实参必须实现了所有接口约束。第十三章详细讨论。

类型参数约束

还有一种次要约束称为类型参数约束,有时也称为裸类型约束。这种约束用的比接口约束少得多。

它允许一个泛型类型或方法规定:指定的类型实参要么就是约束的类型,要么就是约束的类型的派生类。

一个类型参数可以指定零个或者多个类型参数约束,下面这个泛型方法演示了如何使用类型参数约束:

private static List<TBase> ConvertIList<T,TBase>(IList<T> list) where T:TBase{
    List<TBase> baseList=new List<TBase>(list.Count);
    for(Int32 index=0;index<list.Count;index++){
        baseList.Add(list[index]);
    }
    return baseList;
}

ConvertIList方法指定了两个类型参数,其中T参数由TBase类型参数约束。

意味着不管为T指定什么类型实参,都必须兼容于TBase指定的类型实参。

下面这个方法演示了对ConvertIList的合法调用和非法调用:

//构造并初始化一个List<String>,他实现了Ilist<String>
IList<String ls=new List<String>();
ls.Add("A String");
//1.将IList<String>转换成一个IList<Object> IList<Object> lo=ConvertIList<String,Object>(ls);
//2.将IList<String>转换成一个IList<IComparable> IList<IComparable> lc=ConvertIList<String,IComparable>(ls);
//3.将IList<String>转换成一个IList<Icomparable<String>> Ilst<IComparable<String>> lcs=ConvertIList<String,Icomparable<String>>(ls);
//4.将IList<String>转换成一个IList<String> IList<String> ls2=ConvertIList<String,String>(ls);
//5.将IList<String>转换成一个IList<Exception> IList<Exception> ls2=ConvertIList<String,Exception>(ls);//错误

12.8.3 构造器约束

类型参数可以指定零个或者一个构造器约束,它向编译器承诺类型实参是实现了公共无参构造器的非抽象类型。

注意如果同时使用构造器约束和struct约束,编译器会认为这是一个错误,因为这是多余的。

所有值类型都隐式提供了公共无参构造器,以下实例类使用构造器约束来约束他的类型参数:

internal sealed class PrimaryConstraintOfStruct<T> where T:new(){
    public void T Factory(){
        //允许,因为所有值类型都隐藏有一个公共无参构造器
        //而如果指定的是引用类型约束也要求它提供公共无参构造器
        return new T();
    }
}

这个例子中的new T()是合法的,因为已知T是拥有公共无参构造器的类型。对所有值类型来说这一点肯定成立

12.8.4 其他可验证性问题

原文地址:https://www.cnblogs.com/errornull/p/9902710.html