1. 程式人生 > >CLR via C#學習筆記-第十二章-可驗證性和約束

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 其他可驗證性問題