协变与逆变详细解读
協變與逆變是.Net4.0新加入的概念,我看了很多博客文章,可能是我悟性比較差,感覺沒有完全講明白,自己研究了一天終于搞懂了,特此記錄一下。
一、簡單理解協變和逆變
//協變:子類對象(引用)賦值給父類變量(引用) object obj = null; string str = ""; obj = str; //逆變:父類對象(引用)賦值給子類變量(引用) object obj = null; string str = ""; str = obj; /* str = obj;這段代碼大家會發現是錯誤的,這個賦值是基本操作都是錯誤的, 那又如何實現逆變這種逆反的賦值操作呢?實際上逆變根本不是逆向將父類 賦給子類的,我慢慢解釋... */二、真正的協變和逆變
概念:
1、以前的泛型系統(或者說沒有in/out關鍵字時),是不能“變”的,無論是“逆”還是“順(協)”。
2、當前僅支持接口和委托的逆變與協變 ,不支持類和方法。但數組也有協變性。
3、值類型不參與逆變與協變。
協變:Foo<ParentClass> = Foo<ChildClass>
public class TestOut<T> where T : new() {/** 關鍵字out因為協變的類型T只能作為輸出參數使用,而不能作為輸入參數*///*****[協變]泛型委托Demo*****//1、創建泛型委托public delegate T MyFunA<T>(); //默認不支持協變與逆變public delegate T MyFunB<out T>(); //設置支持協變//public delegate void MyFunC<out T>(T param);//錯誤,協變類型只能“出”不能“入”//2、創建委托變量public MyFunA<object> FunAObject = null;public MyFunA<string> FunAString = null;public MyFunB<object> FunBObject = null;public MyFunB<string> FunBString = null;public MyFunB<int> FunBInt = null;//3、驗證結果public void TestFun(){//FunAObject = FunAString;//錯誤,不可用協變FunBObject = FunBString;//正確,可用協變可以完成子類string到父類object的轉換//FunBObject = FunBInt; //錯誤,值類型不參與協變 }//*****[協變]泛型接口Demo*****//1、創建泛型接口public interface IMyInterfaceA<T> { } //默認不支持協變與逆變public interface IMyInterfaceB<out T> { } //設置支持協變//public interface IMyInterfaceC<out T>//{// void Test(T param);//錯誤,協變只能“出”不能“入”//}//2、創建接口變量public IMyInterfaceA<object> interAObject = null;public IMyInterfaceA<string> interAString = null;public IMyInterfaceB<object> interBObject = null;public IMyInterfaceB<string> interBString = null;public IMyInterfaceB<int> interBInt = null;//3、驗證結果public void TestInterface(){//interAObject = interAString; //錯誤,不可用協變interBObject = interBString; //正確,可用協變可以完成子類string到父類object的轉換//interBObject = interBInt; //錯誤,值類型不參與協變 } }逆變:Foo<ChildClass> = Foo<ParentClass>
public class TestIn<T> where T : new() {/** 關鍵字in因為逆變的類型只能作為輸入參數,而不能作為輸出參數*///*****[逆變]泛型委托*****//1、創建泛型委托public delegate void MyActionA<T>(T param); //默認不支持協變與逆變public delegate void MyActionB<in T>(T param); //設置支持逆變//public delegate T MyActionC<in T>();//錯誤,逆變只能“入”不能“出”//2、創建委托變量public MyActionA<object> ActionAObject = null;public MyActionA<string> ActionAString = null;public MyActionB<object> ActionBObject = null;public MyActionB<string> ActionBString = null;public MyActionB<int> ActionBInt = null;//3、驗證結果public void TestAction(){//ActionAString = ActionAObject; //錯誤,不可用逆變ActionBString = ActionBObject; //正確,可用逆變可以完成從父類object到子類string的轉換//ActionBInt = ActionBObject; //錯誤,值類型不參與逆變 }//*****[逆變]泛型接口*****//1、創建泛型接口public interface IMyInterfaceA<T> { } //默認不支持協變與逆變public interface IMyInterfaceB<in T> { } //設置支持逆變//public interface IMyInterfaceC<in T>//{// T Test();//錯誤,逆變只能“入”不能“出”//}//2、創建接口變量public IMyInterfaceA<object> interAObject = null;public IMyInterfaceA<string> interAString = null;public IMyInterfaceB<object> interBObject = null;public IMyInterfaceB<string> interBString = null;public IMyInterfaceB<int> interBInt = null;//3、驗證結果public void TestInterface(){//interAString = interAObject; //錯誤,不可用逆變interBString = interBObject; //正確,可用你變可以完成從父類object到子類string的轉換//interBInt = interBObject; //錯誤,值類型不參與逆變 } }三、解析協變與逆變(協變是順序的,逆變并不是逆反的)
這里就是我看別人的博客沒有看懂的地方,研究時從這里卡住了半天,想通后發現豁然開朗,現在分享出來
先創建一個協變接口,一個逆變接口
//*****協變接口***** public interface ITestA<out T> {T Test(); } public class TestA<T> : ITestA<T> {public T Test(){//do something...return default(T);} } //*****逆變接口***** public interface ITestB<in T> {void Test(T p); } public class TestB<T> : ITestB<T> {public void Test(T p){//do something... } }協變解析:
internal class Program {private static void Main(string[] args){//寫法一ITestA<object> testA = new TestA<string>();object obj = testA.Test();//寫法二ITestA<object> testA1 = null;ITestA<string> testA2 = null;testA1 = testA2;obj = testA1.Test();/*執行步驟如下://先調用父類函數public object ITestA<object>.Test(){//發現父類函數為接口,函數體由子類實現,所以...//再調用子類函數public string ITestA<string>.Test(){//do something... }//父類函數調用子類函數,子類函數向外return返回值,由string類型傳至object類型}*///協變“可出不可入”因為由子類函數向父類函數返回值,子類類型小,父類類型大,所以可以進行安全轉換 } }別的博客中看到以上解釋,沒看明白,后來才懂,他的意思是:協變時是子類向父類返回值,值類型是由子到父,可以安全轉換!
[原式就是主觀應該調用的方式,我想調用子類的這個函數]
[變式就是實際運行時調用的方式,先調用父類函數再由父類函數調用子類函數]
逆變解析:
internal class Program {private static void Main(string[] args){//寫法一ITestB<string> testB = new TestB<object>();testB.Test("");//寫法二ITestB<string> testB1 = null;ITestB<object> testB2 = null;testB1 = testB2;testB1.Test("");/*執行步驟如下://先調用父類函數public void ITestB<string>.Test(string param){//發現父類函數為接口,函數體由子類實現,所以...//再調用子類函數public void ITestB<object>.Test(object param){//do something... }//父類函數調用子類函數,并向子類函數傳遞參數由string類型傳至object類型}*///逆變“可入不可出”因為由父類函數向子類函數傳遞參數,父類類型小,子類類型大,所以可以進行安全轉換 } }別的博客中看到以上解釋,沒看明白,后來才懂,他的意思是:逆變時是父類向子類傳參數值,值類型是由子到父,可以安全轉換!
[原式就是主觀應該調用的方式,我想調用子類的這個函數]
[變式就是實際運行時調用的方式,先調用父類函數再由父類函數調用子類函數]
?
調用執行步驟:
ITestA<object> testA = new TestA<string>();
object obj = testA.Test();
ITestB<string> testB = new TestB<object>();
testB.Test("");
父類變量(引用)調用方法,實際上執行步驟如下:
1、調用父類自己的方法
2、被告知方法體由子類實現
3、父類去調用子類方法
4、【逆變】發現子類方法有參,于是父類傳遞自己的參數(類型string)到子類(類型object),可以安全轉換
5、子類執行方法體功能
6、【協變】將執行的返回值返回給父類
7、【協變】父類接收子類方法返回值,返回值類型為子類的
8、【協變】繼續向上返回,發現返回值類型不一樣(類型string),所以轉為父類方法的類型返回(類型object),可以安全轉換
?
所以這就是為什么【協變只能返回值】,而【逆變只能傳遞值】,實際協變逆變并沒有父類型轉子類型的過程,都是使用的子類型轉父類型的安全轉換
應用場景:微軟提倡只要是泛型的接口或者委托都希望使用協變逆變,RedSharper也會有相應的提示,這樣做也可以增加【函數傳入參數值】、【函數返回值】的擴展性,何樂而不為呢~
轉載于:https://www.cnblogs.com/taiyonghai/p/6524910.html
總結
- 上一篇: mysql重复数据查询
- 下一篇: js 加alert后才能执行方法