【转】那些年来不晓得的术语、概念:协变、逆变、不变体。【转】那些年做不清楚的术语、概念:协变、逆变、不变体。

by admin on 2018年10月14日

【转】那些年将不亮的术语、概念:协变、逆变、不变体

【转】那些年做不亮堂的术语、概念:协变、逆变、不变体

简述什么是协变性、逆变性、不变性

  • 协变性,如:string->object (子类到父类的更换)
  • 逆变性,如:object->string (父类到子类的转换)
  • 莫变性,基于上面两种状况,不可变。具体下面还做分析。

简述什么是协变性、逆变性、不变性

  • 协变性,如: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;

上面代码没有其他问题。

就我们好定义委托试试:

亚洲必赢手机 1

我X,看人非来啊。为什么从定义之委托却不克协变呢。

自看看系统定义的Func到底和我们从定义的产生啊两样:

public delegate TResult Func<out TResult>();

基本上了一个out,什么不良:

  • out:对于泛型类型参数,out 关键字指定该型参数是协变的。 可以当泛型接口和信托中采取 out 关键字。(来源)
  • in:对于泛型类型参数,in 关键字指定该档参数是逆变的。 可以以泛型接口及寄托中运用 in 关键字。(来源)

那我们可以改由定义委托:

亚洲必赢手机 2

完美!

那么如我们设贯彻逆变性呢:

亚洲必赢手机 3

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

亚洲必赢手机 4

咱发现所有信托参数还易了。本来的返值,改化输入参数才实施。

结论:

  • 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#代码:

亚洲必赢手机 5

结论:

  • 所谓的逆变其实只有是编译后展开了挟持类型转换而已。

以上代码也堪直接写成:

//delegate Tout MyFunc<in Tin, out Tout>(Tin obj);
MyFunc<string, string> str5 = t => "农码一生";
MyFunc<object, object> str6 = t => "农码一生";
MyFunc<string, object> str7 = t => "农码一生";

泛型委托的可变性

预先使用框架定义的泛型委托Func和Action做例子(不了解之请戳)

协变:(string->object)

Func<string> func1 = () => "农码一生";
Func<object> func2 = func1;

逆变:(object->string)

Action<object> func3 = t => { };
Action<string> func4 = func3;

上面代码没有其余问题。

跟着我们团结一心定义委托试试:

亚洲必赢手机 6

我X,看人非来啊。为什么从定义的托却不克协变呢。

自我看看系统定义的Func到底和咱们由定义之发生啊两样:

public delegate TResult Func<out TResult>();

基本上了一个out,什么不良:

  • out:对于泛型类型参数,out 关键字指定该项目参数是协变的。 可以当泛型接口及寄托中采用 out 关键字。(来源)
  • in:对于泛型类型参数,in 关键字指定该类型参数是逆变的。 可以以泛型接口和委托中使 in 关键字。(来源)

那么我们亚洲必赢手机可以修改由定义委托:

亚洲必赢手机 7

完美!

这就是说使我们只要兑现逆变性呢:

亚洲必赢手机 8

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

亚洲必赢手机 9

我们发现整整信托参数还换了。本来的回到值,改化输入参数才实施。

结论:

  • 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#代码:

亚洲必赢手机 10

结论:

  • 所谓的逆变其实只有是编译后开展了要挟类型转换而已。

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

//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>
{ }

然后我们测试协变性:

亚洲必赢手机 11

同一我们要将接口 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;

泛型接口的逆变,编译后一致进行了挟持转换:

亚洲必赢手机 12

本,我们呢得一直写成:

IMotion<Teacher> y3 = new Run<People>();

泛型接口的可变性

随之看框架默认接口:

协变:(子类->父类)

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>
{ }

下一场我们测试协变性:

亚洲必赢手机 13

一律我们需要拿接口 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;

泛型接口的逆变,编译后一样进行了挟持转换:

亚洲必赢手机 14

理所当然,我们呢可以直接写成:

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>();

天赋的本身意识编译成功了,没有其余问题!且还可同时协变、逆变??不对,真的天才了啊?我们运行试试:

亚洲必赢手机 15

亚洲必赢手机 16

总的看我要么太就了,如果的确这么好绕过去,Microsoft又哪里必去作个out、in关键字。

于与一个泛型参数,我们既是想发出协变性又想逆变性,咋办?答案是不可行。这就见面现出第三种植情形,既无可以协变又无可以逆变。称为不变性。

若果(我们在IMotion定义两单章程):

//运动
public interface IMotion<T>
{
    T Show();
    void Match(T t);
}

方我们测试了,代码直接强制转换是免克实现协变、逆变的。那么我们不得不通过out、in来实现。如果现在咱们以泛型参数上加out或in属性会怎么样?:

亚洲必赢手机 17

亚洲必赢手机 18

咱俩发现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关键字,有趣味可以切换版本看看。

不变性

从点我们明白逆变性的代码编译后都见面展开强制转换。假设:那咱们无用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>();

天才的自己发觉编译成功了,没有其它问题!且还得又协变、逆变??不对,真的天才了吗?我们运行试试:

亚洲必赢手机 19

亚洲必赢手机 20

如上所述我或顶光了,如果确实如此爱绕过去,Microsoft又何必去打出个out、in关键字。

对此跟一个泛型参数,我们既然想发生协变性又想逆变性,咋办?答案是不可行。这即会面世第三栽情景,既未得以协变又不得以逆变。称为不变性。

只要(我们于IMotion定义两只艺术):

//运动
public interface IMotion<T>
{
    T Show();
    void Match(T t);
}

上面我们测试了,代码直接强制转换是未能够兑现协变、逆变的。那么我们只能通过out、in来落实。如果今天咱们以泛型参数上加out或in属性会怎样?:

亚洲必赢手机 21

亚洲必赢手机 22

咱们发现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)
    {
        //假设中间有很多逻辑.....       
    }
}

亚洲必赢手机 23

为什么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)
    //{
    //    //假设中间有很多逻辑.....         
    //}
}

亚洲必赢手机 24

此间有一定量个第一点:

  • 传参数(in)是将参数当成父类来之所以,显然好逆变(子类当成父类来为此[里氏替换原则]),但是却未可以管父类当子类来为此(如:子类存在发生若父类没有的方法还是性能)。
  • 返回值(out)返回值类型用父类来接,显然可以协变(父类可以接收一切子类),但也非可用子类接收父类数据(如:父类代表的目标非克强制转给子类[string
    str = (string)objcet])。

。。。是无是起硌更想愈迷糊,想不知底就慢慢想。自己动动手。

假如实在怀念的峰很,就拿它们当成是乌龟的臀部(龟腚\规定)吧,知道是C#举行的同一种植安全范围!

延思考

为什么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)
    {
        //假设中间有很多逻辑.....       
    }
}

亚洲必赢手机 25

为什么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)
    //{
    //    //假设中间有很多逻辑.....         
    //}
}

亚洲必赢手机 26

此地发出个别独重要点:

  • 流传参数(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.html

http://www.cnblogs.com/LoveJenny/archive/2012/03/13/2392747.html

http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html

 

总结

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

协变 -> 比较和谐健康的扭转 ->
子类转父类 [如 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.html

http://www.cnblogs.com/LoveJenny/archive/2012/03/13/2392747.html

http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图