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