詳解C#的協變和逆變

Minotauros發表於2018-12-09

  一、使用協變(Covariance)和逆變(Contravariance )能夠實現陣列之間、委託例項和方法之間、泛型委託例項之間、泛型介面的變數和泛型型別的物件之間、泛型介面的變數之間的隱式轉換;使用協變將允許使用比原指定型別派生程度更大(即更具體的)的型別,使用逆變將允許使用比原指定型別派生程度更小(即更不具體的)的型別;
  1.協變和逆變都只支援引用型別,不支援值型別;
  2.如果泛型介面或泛型委託的型別引數被宣告為協變或逆變,則該泛型介面或泛型委託被稱為變體(Variant);

  二、陣列只支援協變,即支援派生程度更大的型別的陣列隱式轉換為派生程度更小的型別的陣列:

object[] myArray = new string[5];
IComparable[] myOtherArray = new string[5];

  1.此操作不是型別安全的,給上述陣列新增原陣列不相容的物件時會丟擲異常ArrayTypeMismatchException:

//myArray[0] = 10; //此處10會被裝箱為object型別,而object型別的物件不能隱式轉換為string型別

  2.由於值型別不支援協變和逆變,因此下面的轉換是錯誤的:

//object[] myArray = new int[5];
//IComparable[] myOtherArray = new int[5];

  三、委託支援協變和逆變,為匹配委託型別和方法簽名提供更大的靈活性,不僅可以將簽名完全匹配的方法分配給委託例項,還可以通過協變將返回值型別與委託型別的返回值型別相比派生程度更大的方法分配給委託例項;通過逆變將引數型別與委託型別的引數型別相比派生程度更小的方法分配給委託例項:

public class MyBaseClass { }
public class MyClass : MyBaseClass { }
public class MyDerivedClass : MyClass { }
public delegate MyClass MyDelegate(MyClass obj);
public delegate T MyDelegate<T>(T obj);
//對於給定的方法
public staitc MyDerivedClass MyFunc(MyBaseClass obj)
{
    return new MyDerivedClass();
}
//使用時:
MyDelegate myDelegate = MyFunc; //同時使用了協變和逆變
MyDelegate<MyClass> myDelegate = MyFunc;

  四、在定義泛型委託時,可以通過將型別引數宣告為逆變數或協變數來定義變體委託(Variant Delegate),從而使具有協變和逆變轉換關係的不用型別的泛型委託例項之間進行隱式轉換,使用關鍵字out修飾返回值的型別引數以支援協變,使用關鍵字in修飾引數的型別引數以支援逆變:

public delegate U MyDelegate<in T, out U>(T obj);
//使用時:
MyDelegate<MyClass,MyClass> myDelegate = MyFunc;
MyDelegate<MyBaseClass, MyDerivedClass> myOtherDelegate = MyFunc;
//隱式轉換:
myDelegate = myOtherDelegate;

  1.變體委託不支援合併,即Delegate.Combine方法需要委託的型別完全相同才能合併,不支援變體委託的轉換;在執行時合併不同型別的變體委託會丟擲異常System.ArgumentException;

  五、在定義泛型介面時,可以通過將型別引數宣告為逆變數或協變數來定義變體介面(Variant Interface),協變允許方法的返回值型別比介面定義中返回值型別引數的派生程度更大,逆變允許方法的引數型別比介面定義中引數型別引數的派生程度更小;使用關鍵字out修飾返回值的型別引數以支援協變,使用關鍵字in修飾引數的型別引數以支援逆變:

public interface IMyInterface<in T, out U>
{
    U MyFunc();
    void MyFoo(T obj);
}
public class MyClass<T, U> : IMyInterface<T, U>
{
    public void MyFoo(T obj)
    {
        //do…
    }
    
}
//使用時:
IMyInterface<string, object> myObj = new MyClass<object, string>();
IMyInterface<object, string> myObj1 = new MyClass<object, string>();
myObj = myObj1;

  1.實現變體介面的型別仍然是不可變數;

 


如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的認可是我寫作的最大動力!

作者:Minotauros
出處:https://www.cnblogs.com/minotauros/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

相關文章