那些年搞不懂的术语、概念:协变、逆变、不变体

那些年搞不懂的术语、概念:协变、逆变、不变体,第1张

概述简述什么是协变性、逆变性、不变性协变性,如:string->object (子类到父类的转换)逆变性,如:object->string (父类到子类的转换)不变性,基于上面两种情况,不可变。具体下面再做分析。泛型委托的可变性先使用框架定义的泛型委托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,什么鬼:out:对于泛型类型参数,out 关键字指定该类型参数是协变的。 可以在泛型接口和委托中使用 out 关键字。(来源)in:对于泛型类型参数,in 关键字指定该类型参数是逆变的。 可以在泛型接口和委托中使用 in 关键字。(来源)那么我们可以修改自定义委托:完美!那如果我们要实现逆变性呢:直接逆变是不可行的,我们需要修改泛型类型参数:我们发现整个委托参数都变了。本来的返回值,改成输入参数才行。结论:in->输入参数->可逆变(父类到子类的转变[如 object->string])out->返回值->可协变(子类到父类的转变[如 string->object]) 假设:如果泛型参数中既存在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)//{// //假设中间有很多逻辑.....//}}这里有两个关键点:传入参数(in)是把参数当成父类来用,显然可以逆变(子类当成父类来用[里氏替换原则]),但是却不可以把父类当子类来用(如:子类存在有而父类没有的方法或属性)。返回值(out)返回值类型用父类来接收,显然可以协变(父类可以接收一切子类),但却不可用子类接收父类数据(如:父类代表的对象不能强制转给子类[string str = (string)objcet])。。。。是不是有点越想越头晕,想不明白就慢慢想。自己动动手。如果实在想的头大,就把它当成是乌龟的屁股(龟腚规定)吧,知道是C#做的一种安全限制!总结关于泛型接口、泛型委托的可变性:协变 -> 比较和谐正常的变化 -> 子类转父类 [如 string转object] -> 必须有out标识 [返回值]逆变 -> 逆天的变化 -> 父类转子类 [如object转string] -> 必须有in标识 [传入参数]  (父亲变儿子,越活越年轻,还不够逆天吗?)所谓的逆变,会在编译后的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;]注意:不支持类的类型参数的可变性只有泛型接口和泛型委托可以拥有可变的类型参数(out、in)可变性只支持引用转换。(不能用于值类型)类型参数使用了 out 或者 ref 将禁止可变性 好了,今天就到这里。没啥高深的技术知识,主要为理解协变、逆变、不变体等术语概念。本文已同步至索引目录:《C#基础知识巩固》 同类文章推荐:http://www.cnblogs.com/haoyifei/p/5760959.htmlhttp://www.cnblogs.com/LoveJenny 简述什么是协变性、逆变性、不变性协变性,如:string->object (子类到父类的转换)逆变性,如:object->string (父类到子类的转换)不变性,基于上面两种情况,不可变。具体下面再做分析。泛型委托的可变性

先使用框架定义的泛型委托Func和Action做例子(

协变:object)

Func<> = () => <> func2 = ;

逆变:string)

Action<> = t =><> func4 = ;

上面代码没有任何问题。

接着我们自己定义委托试试:

我X,看人不来哦。为什么自定义的委托却不能协变呢。

我看看系统定义的Func到底和我们自定义的有什么不同:

TResult Func< TResult>();

多了一个out,什么鬼:

()

完美!

那如果我们要实现逆变性呢:

直接逆变是不可行的,我们需要修改泛型类型参数:

我们发现整个委托参数都变了。本来的返回值,改成输入参数才行。

输入参数->可逆变string])返回值->可协变object])

假设:如果泛型参数中既存在in又存在out改如何:

Tout MyFunc< Tin, Tout>(Tin obj);MyFunc<,> str1 = t => <,> str2 = str1;string)MyFunc<,> str3 = str1;object)MyFunc<,> str4 = str1;

以上都是没有问题的。 

然后我们看看编译后的C#代码:

以上代码也可以直接写成:

(Tin obj);MyFunc<,> str5 = t => <,> str6 = t => <,> str7 = t => ;泛型接口的可变性

接着看框架默认接口:

协变:父类)

IEnumerable<> List = List<><> List2 = List;

逆变: 子类)

IComparable<> List3 = <> List4 = List3;

接下来我们试试自定泛型接口:

首先定义测试类型、接口:

IMotion Run : IMotion

然后我们测试协变性:

同样我们需要把接口  IMotion 定义为  IMotion< T> 

IMotion< T>{}IMotion x = Run y = x;

如果我们要测试逆变性,则需要把  IMotion  定义为  IMotion< T> 

IMotion< T>{}IMotion x2 = Run y2 = x2;

泛型接口的逆变,编译后同样进行了强制转换:

当然,我们也可以直接写成:

IMotion y3 = Run();不变性

从上面我们知道逆变性的代码编译后都会进行强制转换。假设:那我们不用out、in直接手动强制转换是否可以?:

IMotion Run : IMotion { }IMotion x = Run y = x;

<span >//<span >逆变
IMotion x2 = <span >new Run<span >();
IMotion y2 = <span >(IMotion<span ><span >)x2;
IMotion y3 = <span >(IMotion)<span >new Run();

天才的我发现编译成功了,没有任何问题!且还可以同时协变、逆变??,真的天才了吗?我们运行试试:

看来我还是太单纯了,如果真的这么容易绕过去,Microsoft又何必去搞个out、in关键字。

对于同一个泛型参数,我们既想有协变性又想逆变性,咋办?答案是不可行。这就会出现第三种情况,既不可以协变又不可以逆变。称为不变性。

IMotion

上面我们测试过,代码直接强制转换是不能实现协变、逆变的。那么我们只能通过out、in来实现。如果现在我们在泛型参数添加out或in属性会如何?:

我们发现out和in都不能用。在用out时,有个传入参数为泛型  Match(T t) 的方法。使用in时,有个返回参数为泛型  的方法。现在就出现了是矛更锋利,还是盾更坚硬的问题了。

最后结果是:都不能用,既不能协变,也不能逆变。此为不变体。

小知识:

C#4.0之前  、  、  等接口都不支持可变性,在4.0及之后才支持。因为4.0之前定义的泛型接口没有添加out、in关键字,有兴趣可以切换版本看看。

延伸思考

为什么in[输入参数]就只能逆变?分析如下:

{ ; <span >//<span >运动
<span >public
<span >interface
IMotion<<span >in
T><span >
{
<span >voID
<span > Match(T t);
}
<span >//
<span >跑步

<span >public
<span >class
Run : IMotion<span >
{
<span >public
<span >voID
<span > Match(T t)
{
<span >//
<span >假设中间有很多逻辑.....

<span > }
}

为什么out[返回值]只能协变?分析如下:

Salary { ; <span >//<span >运动
<span >public
<span >interface
IMotion<<span >out
T><span >
{
<span >T Show();
<span >//
<span >voID Match(T t);

<span >}
<span >//
<span >跑步

<span >public
<span >class
Run : IMotion<span >
{
<span >public
<span > T Show()
{
<span >return
<span >default
<span >(T);
}
<span >//
<span >public voID Match(T t)
<span >//
<span >{
<span >//
<span >//
<span >假设中间有很多逻辑.....
<span >//
<span >}

}

这里有两个关键点:

传入参数(in)是把参数当成父类来用,显然可以逆变(子类当成父类来用[]),但是却不可以把父类当子类来用。返回值(out)返回值类型用父类来接收,显然可以协变(父类可以接收一切子类),但却不可用子类接收父类数据

。。。是不是有点越想越头晕,想不明白就慢慢想。自己动动手。

如果实在想的头大,就把它当成是乌龟的屁股吧,知道是C#做的一种安全限制!

总结

关于泛型接口、泛型委托的可变性:

-> 比较和谐正常的变化 -> 子类转父类 [如 string转object] ->  [返回值] -> 逆天的变化 -> 父类转子类 [如object转string] ->  [传入参数]  所谓的逆变,会在编译后的C#代码中进行强制类型转换。示例:

IEnumerable<> list = new List<>();  IEnumerable<> list2 = list; IEnumerable<> list2 = new List<>();  

IComparable<> list3 = null;IComparable<> list4 = list3; list4 =

注意:

不支持类的类型参数的可变性只有泛型接口和泛型委托可以拥有可变的类型参数(out、in)可变性只支持引用转换。(不能用于值类型)类型参数使用了 out 或者 ref 将禁止可变性

好了,今天就到这里。没啥高深的技术知识,主要为理解协变、逆变、不变体等术语和概念。

本文已同步至索引目录:《》

同类文章推荐:

href="http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.HTML" target="_blank">http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.HTML

总结

以上是内存溢出为你收集整理的那些年搞不懂的术语、概念:协变、逆变、不变体全部内容,希望文章能够帮你解决那些年搞不懂的术语、概念:协变、逆变、不变体所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1264196.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-08
下一篇 2022-06-08

发表评论

登录后才能评论

评论列表(0条)

保存