標籤:

對於C#裡面的協變和逆變的理解的問題?

static void Main(string[] args)
{
IEnumerable& strs = new List&();
IEnumerable& objs = strs;

//List& strs = new List&();
//List& objs = strs;

Action& actObj = null;
Action& actStr = actObj;
}

.NET 4.0環境

以上代碼,協變部分,為何IEnumerable那樣賦值是編譯正確而List聲明卻編譯報錯?

逆變部分,如果只能將Action&往Action&,這個究竟有什麼意義?


這裡有一篇文章 http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html

仔細看幾遍,會有收穫的。


1、因為IEnumeable&,當然本質上是因為IEnumerable&中的T只用於輸出(只出現在返回值),所以可以加out標記,而IList&中的T用於輸入和輸出(出現在參數和返回值),不能逆變協變。

2、協變和逆變的意義在於避免不必要的類型轉換,簡化代碼和提高性能


IEnumerable是只讀的,List是可以改的。如果List&可以變成List&的話,那我往裡面放一個int怎麼辦?原理我就不講了,反正你只要知道是因為這樣就可以了。

BTW,只有interface可以協變逆變,class是不行的。不過跟上面說的一樣,IList&也不行。


這個問題簡單想想就明白了吧,List&的T既可以用來輸入又可以用來輸出,當然不行。

倒不如想想為什麼IEnumerable&不能變成IEnumerable&


額,樸素點說,協變逆變指的是模板參數(就是那個T啦)和模板類(比如IEnumerable&<&>,List&<&>,Action&<&>,之類的,我們叫他Module&<&>好了)之間保持「is a」關係的聯動。如果T變寬泛,Module&<&>也變寬泛,那就是協變(協變嘛,就是協助變話唄。額,既然評論里有人說了,其實這裡如果按照covariant的原文理解,約莫是跟著一次變的意思。那這個中文翻譯又是幾個意思?)。如果T變寬泛,Module&<&>反而變狹隘了,就是逆變(反著變唄)。如果T變寬泛以後Module&<&>變的和以前完全不同了,就叫不變,互相不能賦值。怎麼定義變寬泛呢?就看變之前和變之後是不是is a的關係。反之就是變狹隘

看IEnumerable &和IEnumerable &,從int到object,變寬泛了,因為int is an object。那麼IEnumerable &是不是IEnumerable &呢?當然是。比如IEnumerable &能幹的事情,IEnumerable &應該都能幹才對。IEnumerable &能幹啥呢?比如循環call每一個object的.toString(),IEnumerable &應該也可以撒。所以IEnumerable & is a IEnumerable &。所以IEnumerable &裡面的T是協變參數。

為啥List&里的T不是協變,而是不變呢?就像上邊好多人說的List&能幹一件事情,就是.add("somestring"),List&行么?所以說(mutable)List&不是一個List&。當然List&更不可能是List&了。List&能「對每個成員++」,List&就不行。所以List&裡面的T一旦範圍變了,變化後的List&跟之前的List&互相都沒有 is a 的關係,就是不變。

那Action&怎麼解呢,關鍵看這個Action本身的定義。假裝說這個Action&表示的是可以接收一個T類型參數的函數。例如Action&表示接受一個string當參數的函數。那Action&呢?表示某個接受一個object當參數的函數。那麼,接受一個object的函數,是不是也可以接受一個string呢?當然,你就把string當成object傳進去就好了。所以Action&是一個Action&。而我們又有string is an object。所以這裡的Action&里的T是逆變(也有叫反變的)


推薦閱讀:

Node.js和.Net 4.5下的await、async相比有什麼不同?
.net最適合做什麼?
學了C#語言可以從事哪些工作?
為什麼沒有新的支持底層達到類似C++這種程度,而易用性達到C#的語言出現?
老師說linq語句過時了,是真的嗎?

TAG:NET | C# |