1. 程式人生 > >c#中泛型的協變與逆變:詳解

c#中泛型的協變與逆變:詳解

in(泛型修飾符)(C# 參考)

Visual Studio 2013 其他版本 0(共 1)對本文的評價是有幫助 評價此主題

對於泛型型別引數,in 關鍵字指定該型別引數是逆變的。 可以在泛型介面和委託中使用 in 關鍵字。

通過逆變,可以使用與泛型引數指定的派生型別相比,派生程度更小的型別。 這樣可以對委託型別和實現變體介面的類進行隱式轉換。 引用型別支援泛型型別引數中的協變和逆變,但值型別不支援。

在泛型介面或委託中,如果型別形參僅用作方法返回型別,而不用於方法實參,則可宣告為協變的。 Ref 和 out 引數不能為變體。

如果介面具有逆變型別形參,則允許其方法接受與介面型別形參指定的派生型別相比,派生程度更小的型別的實參。 

例如,由於在 .NET Framework 4 的  介面中,型別 T 是逆變的,因此如果 Employee 繼承 Person,則無需使用任何特殊轉換方法,就可以將 IComparer(Of Person) 型別的物件指派給 IComparer(Of Employee) 型別的物件。

可以向逆變委託分配同一型別的其他委託,但需使用派生程度較小的泛型型別引數。

示例

下例演示如何宣告、擴充套件和實現一個逆變泛型介面。 此外還演示瞭如何對實現此介面的類使用隱式轉換。

C#
// Contravariant interface.
interface IContravariant<in
A> { } // Extending contravariant interface. interface IExtContravariant<in A> : IContravariant<A> { } // Implementing contravariant interface. class Sample<A> : IContravariant<A> { } class Program { static void Test() { IContravariant<Object> iobj = new
Sample<Object>(); IContravariant<String> istr = new Sample<String>(); // You can assign iobj to istr because // the IContravariant interface is contravariant. istr = iobj; } }

下例演示如何宣告、例項化和呼叫一個逆變泛型委託。 此外還演示瞭如何隱式轉換委託型別。

C#
// Contravariant delegate.
public delegate void DContravariant<in A>(A argument);

// Methods that match the delegate signature.
public static void SampleControl(Control control)
{ }
public static void SampleButton(Button button)
{ }

public void Test()
{

    // Instantiating the delegates with the methods.
    DContravariant<Control> dControl = SampleControl;
    DContravariant<Button> dButton = SampleButton;

    // You can assign dControl to dButton
    // because the DContravariant delegate is contravariant.
    dButton = dControl;

    // Invoke the delegate.
    dButton(new Button()); 
}

具體要看維基百科的解釋:http://zh.wikipedia.org/wiki/%E5%8D%8F%E5%8F%98%E4%B8%8E%E9%80%86%E5%8F%98

還是維基給力,摘錄其中一段:

首先考慮陣列型別構造器: 從Animal型別,可以得到Animal[](“animal陣列”)。 是否可以把它當作

  • 協變:一個Cat[]也是一個Animal[]
  • 逆變:一個Animal[]也是一個Cat[]
  • 以上二者均不是(不變)?

如果要避免型別錯誤,且陣列支援對其元素的讀、寫操作,那麼只有第3個選擇是安全的。Animal[]並不是總能當作Cat[],因為當一個客戶讀取陣列並期望得到一個Cat,但Animal[]中包含的可能是個Dog。所以逆變規則是不安全的。

反之,一個Cat[]也不能被當作一個Animal[]。因為總是可以把一個Dog放到Animal[]中。在協變陣列,這就不能保證是安全的,因為背後的儲存可以實際是Cat[]。因此協變規則也不是安全的—陣列構造器應該是不變。注意,這僅是可寫(mutable)陣列的問題;對於不可寫(只讀)陣列,協變規則是安全的。

這示例了一般現像。只讀資料型別(源)是協變的;只寫資料型別(匯/sink)是逆變的。可讀可寫型別應是“不變”的。

更深入瞭解,請看此文章,個人看了之後豁然開朗。

先不考慮型別,我們考慮數學意義上的整數。考慮整數之間的小於關係——≤。這裡關係實際上就是一個方法,接受2個數,返回布林值。

現在我們考慮一下對於整數的投影,投影指的是一個函式,接受一個整數,返回另一個整數。比如 z → z + z 我們可以定義為D(double), z → 0 - z定義為N for (negate), z → z * z,定義為S(square).

問題就出來了,是不是所有的情況下, (x ≤ y) = (D(x) ≤ D(y))?事實上就是,如果x小於等於y,那麼x的2倍也小於等於y的2倍。投影D保留了不等號的方向。

對於N,很顯然,1 ≤ 2 但是 -1 ≥ -2。即(x ≤ y) = (N(y) ≤ N(x)),投影N反轉了不等號的方向。

對於S, -1 ≤ 0, S(0) ≤ S(-1),但是 1 ≤ 2, S(2)≥ S(1)。可見投影S即沒保留不等號方向,也沒反轉不等號方向。

投影D就是協變的,它保留了整數上的次序關係。投影N是逆變的,它反轉了整數上的次序關係,投影S兩者都不是,所以是不變的。

所以這裡很清楚,整數自身不是變體,小於關係也不是變體。投影才是協變或者逆變——接受一個整數生成一個新整數。