深入理解泛型(二) 協變性和逆變性

2021-08-04 00:14:45 字數 3175 閱讀 4928

引言:在c# 2.0中泛型並不支援可變性的(可變性指的就是協變性和逆變性),我們知道在物件導向的繼承中就具有可變性,當方法宣告返回型別為stream,我們可以在實現中返回乙個filestream的型別,此時就存在乙個隱式的轉化——從filestream型別(子類引用)——>stream型別(父類引用),並且引用型別的陣列也存在這種從子類引用——>父類引用的轉化,例如string 可以轉化為object(即這樣的**是可以通過編譯的:string strs =new string[3]; object objs =strs;),此時我們肯定會想是否泛型中的泛型引數也可以支援這樣的轉化呢?然而在c# 2.0中是不支援的,但是就是因為有這樣的需求,所以微軟也考慮到這個問題的, 所以在c# 4.0中就引入了泛型的協變和逆變性。下面就具體來介紹下c# 4.0 中對協變和逆變的具體內容有哪些的。

一、協變性

協變性指的是——泛型型別引數可以從乙個派生類隱式轉化為基類(大家可以這樣記憶的,協變性即和諧的變化,生活中我們一般會說子女長的像他們的父母,這樣聽起來會感覺比較和諧點,這樣就很容易記住協變了)

,在c#4.0中引入out關鍵字來標記泛型引數支援協變性。為了更好的說明泛型的協變性,下面就以.net類庫的中public inte***ce ienumerable這個介面來演示乙個例子來幫助大家理解泛型協變:

listlistobject = new list();

listliststrs = new list();

// addrange方法接收的引數型別為ienumerablecollection

// 下面的**是傳入的是list型別的引數。

// 在msdn中可以看出這個介面的定義為——ienumerable。

// 所以 ienumerable泛型型別引數t支援協變性,所以可以

// 將list轉化為ienumerable(這個是繼承的協變性支援的)

// 又因為這個ienumerable介面委託支援協變性,所以可以把ienumerable轉化為——>ienumerable型別。

// 所以編譯器驗證的時候就不會出現型別不能轉化的錯誤了。

listobject.addrange(liststrs); //成功

liststrs.addrange(listobject); // 出錯

**中如果使用 這**時 liststrs.addrange(listobject); 就會出現編譯時錯誤(無法從list轉換為ienumerable,因為list可以因為繼承的協變性轉化為ienumerable,但是因為ienumerable不支援逆變,即從object到string的轉化,所以此時就會產生下面圖中的錯誤了。), 錯誤提示截圖如下:

二、逆變性

逆變性指的是——泛型型別引數可以從乙個基類隱式轉化為派生類(可以從生活中的例子來幫助大家記憶逆變的——如果說父母長的像他們的子女的話肯定覺得彆扭,在高中語文中經常會找這樣的語病的)

,在c# 4.0中引入in關鍵字來標記泛型引數支援逆變性.為了更好的說明泛型的逆變性,下面就以.net類庫的中介面public inte***ce icomparer來演示乙個例子來幫助大家理解泛型逆變:

class program

}public class testcomparer : icomparer

}

上面**中如果使用 listobject.sort(objcomparer2);時,就會出現編譯錯誤,錯誤原因看過上面協變中錯誤原因的解釋應該都可以明白的,下面是錯誤的截圖:

為了進一步說明泛型的協變和逆變是在c# 4.0中(c# 4.0即對於.net framework 4.0)的版本都不支援泛型的協變和逆變,大家從msdn中也可以發現的。下面是一張比較的截圖(大家可以自己具體去msdn上檢視的, 當版本改為3.5或更低階的版本時,看下泛型的定義是不是沒有out或in關鍵字,即之前的版本不支援泛型的可變性):

三、協變和逆變的注意事項

並不是所有型別都支援泛型的協變和逆變的, 下面列出泛型的協變和你逆變中值得注意和明確的地方:

1. 只有介面和委託支援協變和逆變(如func,action),類或泛型方法的型別引數都不支援協變和逆變。

2. 協變和逆變只適用於引用型別,值型別不支援協變和逆變(因為可變性存在乙個引用轉換,而值型別變數儲存的就是物件本身,而不是物件的引用),所以list無法轉化為ienumerable.

3. 必須顯示用in或out來標記型別引數。

4. 委託的可變性不要再多播委託中使用,相信這點很多人都沒有注意到的, 下面我舉個例子來說明下,當大家遇到這樣的問題可以知道為什麼:

//

下面初始化委託使用了lambda表示式,lambda表示式將在後面專題向大家具體介紹

func stringfunc = () => ""

; func

objectfunc = () => new

object

(); func

combined = stringfunc + objectfunc;

上面**可以通過編譯,因為泛型func支援協變,所以將func轉換為func型別,但是物件本身仍然為func型別,然而delegate.combine方法要求引數必須為相同型別——否則該方法無法確定要建立什麼型別的委託(是func型別呢還是func?),所以上面**在執行時會丟擲argumetexception(錯誤資訊為——委託必須具有相同的型別)。我們可以稍微修改下上面**來使其不出現執行時錯誤

func stringfunc = () => ""

;

//轉換 委託型別

func tempfunc = new func(stringfunc);

func

objectfunc = () => new

object

(); func

combined = tempfunc + objectfunc;

C 的協變性和逆變性

協變性。ienumerable string strings newlist string 引數 型別派生程度較大的例項化物件 分配給引數型別派生程度較小的物件引用。分配相容性被保留。ienumerable object objects strings 逆變性。假定我們有這麼個方法 static v...

深入理解泛型(一)

一 型別推斷 在我們寫泛型 的時候經常有大量的 符號,這樣有時候 一多,也難免會讓開發者在閱讀 過程中會覺得有點暈的,此時我們覺得暈的時候肯定就會這樣想 是不是能夠省掉一些 符號的呢?你有這種需求了,當然微軟這位好人肯定也會幫你解決問題的,這樣就有了我們這部分的內容 型別推斷 意味著編譯器會在呼叫乙...

關於Final關鍵字和不變性的深入理解

人已走 車未動心已動 身未起若有所思 茫然無措 不變性和final的關係?面試題推薦閱讀 jvm類載入 如果物件在被建立後,狀態就不能被修改,那麼它就是不可變的。具有不可變的物件一定是執行緒安全的,我們不需要對其採取任何額外的安全措施,也能保證執行緒安全。類防止被繼承 方法防止被重寫 變數防止被修改...