先使用框架定義的泛型委托Func和Action做例子(不了解的請戳)
協變:(string->object)
Func<string> func1 = () => "農碼一生"; Func<object> func2 = func1;
逆變:(object->string)
Action<object> func3 = t => { }; Action<string> func4 = func3;
上面代碼沒有任何問題。
接著我們自己定義委托試試:
我X,看人不來哦。為什麼自定義的委托卻不能協變呢。
我看看系統定義的Func到底和我們自定義的有什麼不同:
public delegate TResult Func<out TResult>();
多了一個out,什麼鬼:
那麼我們可以修改自定義委托:
完美!
那如果我們要實現逆變性呢:
直接逆變是不可行的,我們需要修改泛型類型參數:
我們發現整個委托參數都變了。本來的返回值,改成輸入參數才行。
結論:
假設:如果泛型參數中既存在in又存在out改如何:
delegate Tout MyFunc<in Tin, out Tout>(Tin obj);
MyFunc<object, string> str1 = t => "農碼一生"; MyFunc<string, string> str2 = str1;//第一個泛型的逆變(object->string) MyFunc<object, object> str3 = str1;//第二個泛型的協變(string->object) MyFunc<string, object> str4 = str1;//第一個泛型的逆變和第二個泛型的協變
以上都是沒有問題的。
然後我們看看編譯後的C#代碼:
結論:
以上代碼也可以直接寫成:
//delegate Tout MyFunc<in Tin, out Tout>(Tin obj); MyFunc<string, string> str5 = t => "農碼一生"; MyFunc<object, object> str6 = t => "農碼一生"; MyFunc<string, object> str7 = t => "農碼一生";
接著看框架默認接口:
協變:(子類->父類)
IEnumerable<string> list = new List<string>(); IEnumerable<object> list2 = list;
逆變:(父類-> 子類)
IComparable<object> list3 = null; IComparable<string> list4 = list3;
接下來我們試試自定泛型接口:
首先定義測試類型、接口:
// 人 public class People { } //老師(繼承People[人]) public class Teacher : People { } //運動 public interface IMotion<T> { } //跑步 public class Run<T> : IMotion<T> { }
然後我們測試協變性:
同樣我們需要把接口 interface IMotion<T> 定義為 interface IMotion<out T>
//運動 public interface IMotion<out T>{}
IMotion<Teacher> x = new Run<Teacher>(); IMotion<People> y = x;
如果我們要測試逆變性,則需要把 interface IMotion<T> 定義為 interface IMotion<in T>
//運動 public interface IMotion<in T>{}
IMotion<People> x2 = new Run<People>(); IMotion<Teacher> y2 = x2;
泛型接口的逆變,編譯後同樣進行了強制轉換:
當然,我們也可以直接寫成:
IMotion<Teacher> y3 = new Run<People>();
從上面我們知道逆變性的代碼編譯後都會進行強制轉換。假設:那我們不用out、in直接手動強制轉換是否可以?:
// 人 public class People { } //老師(繼承People[人]) public class Teacher : People { } //運動 public interface IMotion<T> { } //跑步 public class Run<T> : IMotion<T> { }
//協變 IMotion<Teacher> x = new Run<Teacher>(); IMotion<People> y = (IMotion<People>)x; //逆變 IMotion<People> x2 = new Run<People>(); IMotion<Teacher> y2 = (IMotion<Teacher>)x2; IMotion<Teacher> y3 = (IMotion<Teacher>)new Run<People>();
天才的我發現編譯成功了,沒有任何問題!且還可以同時協變、逆變??不對,真的天才了嗎?我們運行試試:
看來我還是太單純了,如果真的這麼容易繞過去,Microsoft又何必去搞個out、in關鍵字。
對於同一個泛型參數,我們既想有協變性又想逆變性,咋辦?答案是不可行。這就會出現第三種情況,既不可以協變又不可以逆變。稱為不變性。
如(我們在IMotion定義兩個方法):
//運動 public interface IMotion<T> { T Show(); void Match(T t); }
上面我們測試過,代碼直接強制轉換是不能實現協變、逆變的。那麼我們只能通過out、in來實現。如果現在我們在泛型參數添加out或in屬性會如何?:
我們發現out和in都不能用。在用out時,有個傳入參數為泛型 void Match(T t) 的方法。使用in時,有個返回參數為泛型 T Show() 的方法。現在就出現了是矛更鋒利,還是盾更堅硬的問題了。
最後結果是:都不能用,既不能協變,也不能逆變。此為不變體。
小知識:
C#4.0之前 IEnumerable<T> 、 IComparable<T> 、 IQueryable<T> 等接口都不支持可變性,在4.0及之後才支持。因為4.0之前定義的泛型接口沒有添加out、in關鍵字,有興趣可以切換版本看看。
為什麼in[輸入參數]就只能逆變?分析如下:
// 人 public class People { } //老師(繼承People[人]) public class Teacher : People { //薪水 public decimal Salary { get; set; } } //運動 public interface IMotion<in T> { void Match(T t); } //跑步 public class Run<T> : IMotion<T> { public void Match(T t) { //假設中間有很多邏輯..... } }
為什麼out[返回值]只能協變?分析如下:
// 人 public class People { } //老師(繼承People[人]) public class Teacher : People { //薪水 public decimal Salary { get; set; } } //運動 public interface IMotion<out T> { T Show(); //void Match(T t); } //跑步 public class Run<T> : IMotion<T> { public T Show() { return default(T); } //public void Match(T t) //{ // //假設中間有很多邏輯..... //} }
這裡有兩個關鍵點:
。。。是不是有點越想越頭暈,想不明白就慢慢想。自己動動手。
如果實在想的頭大,就把它當成是烏龜的屁股(龜腚\規定)吧,知道是C#做的一種安全限制!
關於泛型接口、泛型委托的可變性:
IEnumerable<string> list = new List<string>();
IEnumerable<object> list2 = list; //協變
IEnumerable<object> list2 = new List<string>(); //(也可以直接寫成這樣)
IComparable<object> list3 = null;
IComparable<string> list4 = list3; //逆變 編譯後 [ IComparable<string> list4 = (IComparable<string>) list3;]
注意:
好了,今天就到這裡。沒啥高深的技術知識,主要為理解協變、逆變、不變體等術語和概念。
本文已同步至索引目錄:《C#基礎知識鞏固》
同類文章推薦:
http://www.cnblogs.com/haoyifei/p/5760959.html
http://www.cnblogs.com/LoveJenny/archive/2012/03/13/2392747.html
http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html