逆變與協變

2021-10-02 17:14:12 字數 2387 閱讀 9440

一、遇到問題

這是基於.net3.5開發,實際工作中遇到乙個問題。假設我們有乙個 base 類,乙個 derived 類,derived 繼承了 base。如下:

classbase

classderived : base

當我用ienumerable作為形參,list作為實參時,發現編譯出錯了!原本父類作為形參,傳遞子類是再正常不過的,但在泛型中確編譯不通過。

二、**問題

通常我們在設計引數和返回值都有乙個原則,引數要盡可能「泛」(父:父類),返回值要盡可能的「細」(子:子類)。泛,指得是用介面或者父類作為引數,這樣可以接收更多的引數型別;細,指的是返回具體型別,這樣可以更好說明方法的作用。

舉個例子:

stringstrs =newstring;

//這樣的缺點是陣列就傳遞不了了,還要呼叫一次 tolist()

staticvoidtest_1(list<string> list)

//正確的做法,應該用ienumerable

staticvoidtest_2(ienumerable<string> list)

可見,引數的「泛」可以提供更大的靈活性。

接著就進入本次的主題:抗變與協變。需要說明的是,抗變與協變是在4.0開始支援的。假設有乙個方法需要derived集合作為引數,那麼基於上面的原則,我們會這樣設計:

staticvoidtestin(ienumerablebases)

接著我們向下面這樣呼叫,在3.5下就會發現編譯不通過,提示無法將 list轉換為ienumerable。

listlistin =newlist();

testin(listin);

同樣的**,我們拿到4.0下,發現編譯通過了。比較 ienumerable泛型介面,我們發現4.0下的定義為:  

publicinte***ceienumerable<outt> : ienumerable

發現多了 out 關鍵字,這就是協變。msdn對於型別引數的解釋是:out t 要列舉的物件的型別。該型別引數是協變的。即可以使用指定的型別或派生程度更高的型別。

我們可以這樣理解協變,引數的型別就是協變的,父類用子類代替,也就是子類當父類使用。

理解協變後,抗變就好理解了。函式的返回值就是抗變的,子類用父類代替,也就是父類當子類使用。在非泛型的情況下,我們可以這樣接收方法的返回值:

objectobj = test_3();

staticstringtest_3()

當然,我們覺得這樣呼叫也應該是可以的:  

ienumerablelistout = testout();

staticienumerabletestout()

在3.5下,這樣同樣會編譯錯誤。4.0下就沒有問題。

三、總結

協變與抗變的概念其實我們經常遇到(引數協變、返回值抗變),而且我們也會習慣的這樣設計。但對於泛型,.net 到了4.0才提供這樣的支援,這為泛型的使用提供了更大的靈活性。

ok,實際我們不怎麼需要去理解概念性的東西,知道原理和理解怎麼使用即可。以上是我的個人理解,如果有朋友想要更深入的理解,可以參見msdn。

協變與逆變

目錄 1.協變 2.逆變 在泛型之前,我們都知道可以將乙個派生類物件賦值給基類變數,這叫做賦值相容性。看下面這個例子 相容性示意圖如上。但是當我們新增泛型機制時 class animal class dog animal delegate t factory class program static...

C 協變與逆變

用最簡單的最有內涵的方式進行概括 在oo哲學裡面 有個依賴倒置,這是個oo的核心 就是用父類物件可以代表後面也許動態增加的子類物件從而增加了軟體的,可擴充套件性,和相對穩定性,並且開啟了一種oo正規化 class a class a1 a class a2 a 用a a 這個物件可以表達a1,a2....

協變和逆變

協變和逆變都是術語,前者指能夠使用比原始指定的派生型別的派生程度更大的型別,後者指能夠使用比原始指定的派生型別的派生程度更小的型別。using system using system.collections.generic using system.text class derived base s...