C#泛型的抗变与协变

学习自

C#本质论6.0

https://www.cnblogs.com/pugang/archive/2011/11/09/2242380.html

Overview

一直以来,被抗变与协变的定义搞得头昏脑涨掰持不清,如果在加上泛型在其中作祟,就更加两眼发懵了。所以就暂时略过了这一拦路虎,但是今天在学习Kotlin泛型的时候再一次碰到了这里拦路虎,只硬着头皮迎难而上了。

抗边与协变的定义

协变: 子类想父类方向的类型转换称之为协变

//string[] 向 object[] 协变转换
string [] strArray = new string[10];
object [] objArray = strArray;

抗变: 父类向子类方向的类型转换称之为抗变

//委托的抗变
Action<object> actObj = Set;
Action<string> actStr = actObj;

抗变和协变是针对于泛型接口和委托来说的,但是数组也是存在协变的,但是数组的协变是不安全的

泛型的抗变与协变

受限制的泛型协变

在这里有一个让初学泛型的人非常疑惑的一个问题:

** 为什么List 的集合不能复制为List 的集合 **

虽然string可以向object协变转化, 但是因为类型会不安全,这种操作是没法的。

唯恐说不明白,举个例子吧

咱们 假定,下面的代码不会报错,且看我一步步分析

List<string> strList = new List<string>();
//在这里会报错,但是咱们先假设他们可以顺利执行
List<object> objList = strList;
//因为objList实际其中装着的是strList,那么可以肯定将string类型的数据添加到集合中是完全没有问题的
objList.Add("Hello");
//因为objList实际其中装着的是strList,因为string类型和int类型肯定是会有冲突的,假设这样的代码可以执行那么只有崩溃一条路可以走
objList.Add(1);
//这就是为什么,List<string> 不能赋值给List<int>

out 修饰符允许协变性

之所以会出现上面的问题,是因为List 不仅仅可以读取,还可以被写入,这样就造成了类型不安全。所以上述操作是不允许的。因为问题出在了写入上面,所以如果我们提供一个只读的操作是否就可以避免这种错误呢?

看下列的代码

public interface IReadOnlyPair<T>
{
T First { get; } T Second { get; }
} public interface IPair<T>
{
T First { get; set; }
T Second { get; set; }
} public class Pair<T> : IPair<T>, IReadOnlyPair<T>
{
public Pair(T first, T second)
{
} public T First
{
get
{
throw new System.NotImplementedException();
}
set
{
throw new System.NotImplementedException();
}
} public T Second
{
get
{
throw new System.NotImplementedException();
}
set
{
throw new System.NotImplementedException();
}
}
} static void Main(string[] args)
{
Pair<string> strPair = new Pair<string>("First", "Second");
//虽然代码看上去十分合理,但是在这里仍然会报错,因为类型仍然存在不安全
IReadOnlyPair<object> objPair = strPair;
Console.WriteLine(objPair.First);
Console.WriteLine(objPair.Second);
}

IReaderOnlyPair是只读的,所以它根本就没有写入操作,那么这样就避免了上面所提到的因为添加操作而出现的类型安全问题,所以会编译应该不会有问题,那么咱们现在的这个是否能够通过呢?

答案是否定的,因为类型仍然不安全,为了结果这种问题C#4.0提供了out修饰符来允许我们进行类型安全的协变

为什么是不安全的看下面代码就知道了

public interface IReadOnlyPair<T>
{
T First { get; } T Second { get; }
//如果接口中,又多出了下面的方法,那么泛型参数T就又成了了“输入”类型的泛型参数,在一次出现了类型不安全问题
void Set(T t);
}

为了支持协变,我们需要对代码进行一下小小的修改

public interface IReadOnlyPair<out T>
{
T First { get; } T Second { get; }
}

out 修饰符的作用是,告诉编译器,这个泛型参数只用于 输出, 即只用 返回值属性, 经过这些变化之后,上面的代码就不会报错了。

但是协变转换,仍然是有一些限制的

  • 只有泛型委托和泛型接口是可以进行协变转换的,而泛型类是不可以的
  • 参与协变的泛型类型必须是引用类型,而不能是像 int 这样的值类型
  • 接口或者委托声明为支持协变了的话,那么泛型参数只能用作属性返回值 而不能用于 方法的参数

泛型的抗变

抗变相对于协变是相对比较难一点的,但是只要抓住了其中的关键原因,理解起来也非常容易。

直接上例子比讲定义更清晰
static void Main(string[] args)
{
Action<object> actObj = Set;
//在这里你可能会感到奇怪,Object类型怎么可能可以直接地转换为string类型呢
Action<string> actStr = actObj; actStr.Invoke("dfdf");
} static void Set(object obj)
{
Console.WriteLine(obj.ToString());
}

在泛型接口和委托的协变中 遵循了这么一个原则 子类可以安全地调用父类的任何成员 ,上述代码中 actObj 委托指向了Set方法,接下来将actObj赋值给了 Action actStr, 那么现在actStr中存放的实际上是actObj对象指向的方法是 Set(object ojb) 方法,注意string类型可以安全地调用Object类中的所有的成员,所以actStr.Invoke("dfdf"); 这句代码完全没有问题。

in类型修饰符,允许抗变

Action委托是支持抗变的,如果想让自己定义的泛型具有抗变的特性,需要在泛型类型前加上 in 关键字才行。

public delegate void Action<in T>(T obj);

抗变也有着一些规则限制

  • 只能是引用类型
  • 只能作为方法参数,不能用作返回值和属性
  • 只有泛型委托和泛型接口是可以进行抗变转换,而泛型类是不可以的

结语

本文记录了我对泛型的抗变和协变的理解,以供大家参考,如果文中有什么谬误或不足之处欢迎指出。

C#泛型的抗变与协变的更多相关文章

  1. c# 泛型的抗变和协变

    namespace test { // 泛型的协变,T 只能作为返回的参数 public interface Class1<out T> { T Get(); int Count { ge ...

  2. C#泛型中的抗变和协变

    在.net4之前,泛型接口是不变的..net4通过协变和抗变为泛型接口和泛型委托添加了一个重要的拓展 1.抗变:如果泛型类型用out关键字标注,泛型接口就是协变的.这也意味着返回类型只能是T. 实例: ...

  3. 《C#高级编程》学习笔记------抗变和协变

    1.协变和抗变 在.NET 4之前,泛型接口是不变的..NET 4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展.协变和抗变指对参数和返回值的类型进行转换.例如,可以给一个需要Shape参数的 ...

  4. .NET 4.0中的泛型逆变和协变

    转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...

  5. C# 抗变与协变的理解

    我们知道 方法的参数是协变的: void display(shape o) 如果类Rectangle 继承于shape类,那我们可以给该方法传入Rectangle类的实例. 而方法的返回类型是抗变的, ...

  6. C#核心语法讲解-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)

    泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...

  7. C#核心语法-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)

    泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...

  8. 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

    前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...

  9. .NET 4.0中的泛型的协变和逆变

    转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...

随机推荐

  1. sql查询结果存入DataTable,然后从DataTable取数据

    public static DataTable SqlConnectionInformation() { string connstr = ConfigurationManager.Connectio ...

  2. 原始套接字-TCP/IP下三层数据显示

    #include <stdio.h> #include <errno.h> #include <unistd.h> #include <sys/socket. ...

  3. Git与GitHub学习笔记(二)提交的一些笔记

    1.合并分支的使用一定要切换到master分支上去合并:git merge company2.切换分支的时候一定要提交干净本地分支的代码,才可以切换分支,否则提示错误信息: 3.这时候我们做的就是提交 ...

  4. 用matplotlib制作的比较满意的蜡烛图

    用matplotlib制作的比较满意的蜡烛图 2D图形制作包, 功能强大, 习练了很久, 终于搞定了一个比较满意的脚本. 特点: 使用方面要非常简单 绘制出来的图要非常的满意, 具有如下的特点 时间和 ...

  5. chrome 隐藏技能之 base64 图片转换

    有时候我们要转换图片为base64,或者将base64转回图片,可能都需要找一些在线工具或者软件类型的工具才行.当然 chrome 也算是软件,但是好在做前端的都有 chrome.好了,来看下简单的例 ...

  6. python3之模板pycurl探测web服务质量

    1.pycurl简介 pycURL是libcurl多协议文件传输库的python接口,与urllib模块类似,PycURL可用于从python程序中获取由URL标识的对象,功能很强大,libcurl速 ...

  7. [转]html网页 swf播放器使用代码

    <object id="player" height="240" width="275" classid="CLSID:6B ...

  8. 多继承下的super()指向的不一定是直接父类

    常规情况 class Base: def __init__(self): print('Base.__init__') class A(Base): def __init__(self): super ...

  9. Kaggle案例分析3--Bag of Words Meets Bags of Popcorn

    项目描述:这是一个关于情感分析的教程.谷歌的Word2Vec(文本深度表示模型)是一个由深度学习驱动的方法, 旨在获取words内部的含义.Word2Vec试图理解单词之间的含义与语义关系.它类似于r ...

  10. shell升级

    对/sbin/nologin的理解   系统账号的shell使用 /sbin/nologin ,此时无法登陆系统,即使给了密码也不行.   所谓“无法登陆”指的仅是这个用户无法使用bash或其他she ...