泛型(六)可验证性和约束

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

C#的关键字where告诉编译器,为T指定的任何类型都必须实现同类型(T)的泛型IComparable接口,有了这个约束,就可以在方法中调用CompareTo,因为已知IComparable<T>接口定义了CompareTo

现在,当代码引用一个泛型类型或者方法时,编译器要负责保证类型实参符合指定的约束,例如,假如编译以下代码:
 
public static void CallMin(){
    object o1="jeff",o2="richer";
    object oMin=Min<object>(o1,o2);
}

编译器报错,因为System.Object没有实现IComparable<Object>接口,事实上,System.Object没有实现任何接口

 
更深一下研究约束,
 
1.约束可以应用于一个泛型类型的类型参数
2.也可以应用于一个泛型方法的类型参数(像Min方法那样)
3.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{
        
    }
}

error cs0460:重写和显示接口实现方法的约束是从基方法继承的,因此不能直接指定这些约束

 
如过从Derived类的M<T3,T4>方法中移除两个where子句,代码就能编译了,注意,类型参数的名称可以更改,比如将T1改为T2,T3改为T4,但约束不能更改(甚至不能指定)
 
主要约束
 
类型参数可以指定零个或一个主要约束,主要约束可以是一个引用类型,它标识了一个没有密封的类,不能指定以下特殊引用类型
System.Object
System.Array
System.Delegate
System.MulticastDelegate
System.ValueType
System.Enum
System.Void
 
指定一个引用类型约束时,相当月向编译器承诺:一个指定的类型参数要么是月约束类型相同的类型,要么是从约束类型派生的一个类型
 
 
例如下泛型类:
 
internal sealed class PrimaryConstraintOfStream<T> where T:Stream{
    
    public void M(T stream){
        stream.close();    //正确
    }
}


在这个类定义中,类型参数T设置了主要约束Stream,这就告诉编译器,使用PrimaryConstraintOfStream的代码在指定类型实参时,必须指定Sream或者从Stream派生一个类型(FileStream)。如果一个类型参数没有指定主要约束,就默认为System.Object。然而,如果源代码中显示指定System.Object,C#会报告一个错误:error cs0702:约束不能是特殊类“Object”
 
有两个特殊的主要约束:class和struct  
1.class约束向编译器承诺一个指定的类型实参是引用类型。任何类类型,接口类型,委托类型或这是数组类型都满足这个约束。例如以下泛型:

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

2.struct约束向编译器承诺一个指定的类型实参是值类型。包括枚举在内的任何值类型都满足这个约束。然而,编译器和CLR将任何System.Nullable<T>值类型都视为特殊类型,而且可空类型不满足这个struct约束,原因是Nullable<T>类型将他的类型实参约束为struct,而CLR希望禁止像Nullable<Nullable<T>>这样的一个递归类型。

 
例如:
internal sealed class PrimaryConstraintOfStruct<T> where T: struct{
    //允许。因为所有值类型都隐式有一个公共无参构造器    
    return new T();
}
 
次要约束
 
一个类型参数可以指定零个或者多个次要约束,次要约束代表的是一个接口类型。指定一个接口类型约束时,是向编译器承诺一个指定的类型实参是实现了接口的一个类型。由于能指定多个接口约束,所以为类型实参指定的类型必须实现了所有接口约束。
 
还有一种约束成为类型参数约束,有时也称为裸类型约束。这种约束用的比接口约束少得多。它允许一个泛型类或者方法规定:在指定的类型实参之间,必须存在一个关系,一个类型参数可以指定零个或多个类型参数约束。例如
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指定的类型实参。
 
构造器约束
 
一个类型参数可以指定零个或者一个构造器约束。指定构造器约束相当于向编译器承诺:一个指定的类型实参是实现了公共无参构造器的一个非抽象类型。注意,如果同时指定了构造器的约束和struct约束,C#编译器会认为这是一个错误,因为这个是多余的;所有值类型都隐式提供了一个公共无参构造器,例如
 
internal sealed class ConstructorConstraint<T> where T: new (){
    public static T Factory(){
        //允许,因为所有值类型都隐式有一个公共无参构造器
        //而约束要求指定的任何引用类型也要有一个公共无参构造器
        return new T();
    }
}
其他可验证性问题
 
1.泛型类型变量的转型
 
将一个泛型类型的变量转型为另一个类型是非法的,除非将其转型为与一个约束兼容的类型
 
private static void CastingAGenericTypeVariable<T>(T obj){
    Int32 x=(Int32)obj;    //错误
    String x=(String)obj;    //错误
}
上述两行会造成编译报错,因为T可能是任意类型,无法保证转型成功,可以先转型为Object
private static void CastingAGenericTypeVariable<T>(T obj){
    Int32 x=(Int32)(Object)obj;    //无错误
    String x=(String)(Object)obj;    //无错误
}
编译无错,但会抛异常InvalidCastException
如果试图转型一个引用类型,还可以使用C#的as 操作符
private static void CastingAGenericTypeVariable<T>(T obj){
    String s=obj as String;
}
 
2.将一个泛型类型变量设为默认值
 
将泛型类型变量值设为null是非法的,除非泛型类型约束成一个引用类型
 
private static void SettingAGenericTypeVariableToNull<T>(){
 
    T temp=null;    //error Cs043 无法将null转换为类型参数T,因为它可能是不可以为null的值类型。请考虑改用default(T)
    T temp1=default(T); //正确 如果是T值类型,则默认为0
}
 
3.将一个泛型类型变量与null进行比较
 
无论泛型类型是否被约束,使用==或!=操作符将一个泛型类型变量与null进行比较都是合法的:
 
private static void CompareGenericTypeWithNull<T>(T obj)
{
    if(obj==null) { /*对于值类型来说,永远都不会执行*/ }
}
4.两个泛型类型变量相互比较
 
如果泛型类型参数不是一个引用类型,对同一泛型类型的两个变量进行比较是非法的
 
private static oid CompareTwoGenericTypeVariables<T>(T o1,To2){
    if(o1==o2){} //错误
}
T未进行约束,如果两个值类型变量相互比较是非法,除非值类型重载了==操作符。如果T被约束成class,上述代码能通过编译
不允许将类型参数约束成一个具体的值类型,因为值类型是隐式密封的,不可能存在派生,如果允许将类型参数约束成具体的值类型,那么泛型方法会被约束为只支持该具体的类型,这还不如写一个非泛型的方法呢!
 
5.泛型类型变量作为操作数使用
 
将操作符应用于泛型类型的操作数,会出现大量的问题,因为编译器在编译时无法确定类型。
posted @ 2012-11-26 14:48  Lordbaby  阅读(869)  评论(0编辑  收藏  举报