加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 运营中心 > 网站设计 > 教程 > 正文

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

发布时间:2016-10-29 07:14:38 所属栏目:教程 来源:站长网
导读:副标题#e# 简述什么是协变性、逆变性、不变性 协变性,如:string-object (子类到父类的转换) 逆变性,如:object-string (父类到子类的转换) 不变性,基于上面两种情况,不可变。具体下面再做分析。 泛型委托的可变性 先使用框架定义的泛型委托Func和A
副标题[/!--empirenews.page--]

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

  • 协变性,如: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() 的方法。现在就出现了是矛更锋利,还是盾更坚硬的问题了。

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

小知识:

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读