• 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 其他可验证性问题

  • 相关阅读:
    关于数据集的划分--训练集、验证集和测试集
    关于过拟合的问题总结
    paddle 09-13
    关于NLP多分类任务评价指标的总结
    数组题解
    多进程-协程
    多任务-进程
    多任务-线程
    网络-tcp
    网络-udp
  • 原文地址:https://www.cnblogs.com/errornull/p/9902710.html
Copyright © 2020-2023  润新知