畢業快一年了,邊工作邊學習,雖說對.net不算精通,但也算入門了,但一直以來對協變和逆變這個概念不是太了解,上學時候mark了一些文章,今天回過頭看感覺更糊塗了,真驗證本人一句口頭禪“知道的越多,知道的越少”。看到最后實在亂了,就干脆裝糊塗好了,本人也算半個陰謀論者,在編程語言這方面當我實在沒法吃透一個語法的時候,我就歸咎於編譯器這個幕后黑手。我們看下面兩個類Derived派生自Base:
public class Base { } public class Derived:Base { }
我們都這知道下面這兩行代碼,第一行能編譯通過,第二行則無法編譯通過:
Base b=new Derived(); Derived d = new Base();
當我們嘗試編譯第二行代碼的時候,編譯器會提示我們缺少一個顯示類型轉換。那我們加上強制類型轉換后自然就沒問題了。
Derived d = (Derived)new Base();
Why?其實原因很簡單,因為C#語言規范就是這樣的,編譯器就是這么處理的。這有點像宇宙學中的“人擇原理”,當我弄不清楚一個問題我就放空自己。當然隨着人類慢慢探索,對宇宙的了解越來越多,宇宙是現在這樣是有它的道理的,編譯器這樣處理也是有它道理的。下面說下自己對上面為什么子類對象能賦值給父類變量而父類對象不能賦給子類變量的粗俗理解(不談多態)。
每個對象本質上都是內存中的一塊地址空間,當然不同對象占用的地址空間不同。我們聲明一個對象后Base b=new Derived() ,怎么訪問這塊地址空間呢?當然就是通過那個“變量”b。變量的類型就決定了這個變量能“看到”多大的地方,變量就是查看對象的一雙“眼睛”。子類繼承自父類,子類的對象比父類的對象要大些。
父類對象變量的“視角”要比子類對象變量“視角”小。當我們把子類對象賦個父類變量的時候:
Base b=new Derived();
變量b只會看到它能看到的東西,換句話說指針不會訪問到未知的區域,所以這種類型的隱式轉換是安全的,編譯器允許這么做。
反過來如果把一個父類對象賦給子類的變量:
Derived d = new Base();
因為子類變量的視野范圍超過了父類對象的大小,就會看到了不該看到了,換句話說,指針能訪問到不該訪問的區域,這被認為是不安全的,因此編譯器不允許這么做。
那么這和協變和逆變又有什么關系呢?個人認為協變逆變不過是一種隱式類型轉換,.net4.0通過in和out關鍵字保證了在泛型接口和委托上對這種安全的允許的隱式轉換的支持。下面以委托做簡單的說明。
先看協變:
public delegate T Function<out T>(); public delegate void Operate<in T>(T instance);
static void Main(string[] args) { Function<Derived> funDer = new Function<Derived>(() => { return new Derived(); }); Function<Base> funBase = funDer; Base b = funBase.Invoke(); }
首先我想說明下,之前看網上有人說Function<Base> funBase = funDer;這句是“子類對象賦值給父類的變量(這里幸好是委托,如果是接口可能更容易這么覺得),父類調用子類的方法,體現了多態。”因此就得出觀點:“協變體現了多態性”。個人認為這里根本不存在多態的概念,funBase和funDer根本就不是父子類的關系何來多態,相反這里體現的面相對象的另一個特性繼承。本質上就是上面提到的Base b = funBase.Invoke();這里可以安全的進行從Derived到Base的轉換,b不會看到不該看到的。
再來看下逆變:
Operate<Base> opBase = new Operate<Base>(x => { Console.WriteLine(x.ToString()); }); Operate<Derived> opDer = opBase; opDer.Invoke(new Derived());
同樣有人說這里Operate<Derived> opDer = opBase;是“父類變量賦值給子類變量,是4.0種出現的新的特性,以前沒見過。”事實上呢?事實上這里才真正體現了多態性。x => { Console.WriteLine(x.ToString());這里x只會以父類的視角去看傳遞給該方法的參數,只會看到子類中它能看到的(包括重載的方法),這不正是多態的體現嗎?當然也是因為符合上面我提到的類型之間安全的隱式轉換,所以編譯器自然支持這種“逆變”。
泛型接口中的協變和逆變理解起來更難點(一個原因我想是更容易讓人跟傳統的繼承、多態聯系在一起了),但本質上是一樣的。
以上就是我個人對協變和逆變的一些膚淺的理解。其實很多人我想都被這兩個忒專業的術語嚇到了,如果真的理解不了那就暫且不去了解,F1看MSDN:
Covariance permits a method to have return type that is more derived than that defined in the delegate. Contravariance permits a method that has parameter types that are less derived than those in the delegate type. —— MSDN