逆变(Contravariance)/协变(Covariance)

1. 基本概念

官方: 协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。协变/逆变只支持委托、泛型接口。数组由于历史的原因也支持协变/逆变。在 C# 中,协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。 协变保留分配兼容性,逆变则与之相反。协变允许方法具有的返回类型比接口的泛型类型参数定义的返回类型的派生程度更大只有引用类型才支持使用泛型接口中的变体。

协变:IFoo<父类> = IFoo<子类>;interface IFoo<out T>{}

逆变:IBar<子类> =  IBar<父类>;interface IBar<in T>{}

以下代码演示分配兼容性、协变和逆变之间的差异。

string str = "test";
derived type.
object obj = str; // Covariance.
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

//static void SetObject(object o) { }
Action<object> actObject = SetObject;
Action<string> actString = actObject;

2、下面我谈谈个人对协变和逆变的理解

 “协变”相当于隐式转换,基类集合=派生类集合等于让基类集合指针指向派生类集合,基类集合可以用子类集合中方法。对于基类集合只是不能使用基类集合中属于子类的方法,基类集合没有性能上的损失,所以可以做回返回值。

“逆变”相当于强制转换,派生类集合=基类集合, 等于让派生类集合的指针指向基类集合,等于让派生类集合的指针指向基类集合。由于基类的功能小于派生类集合。就造成派生类可使用的方法减少,功能降低。所以不能作为返回值。

我们先来看一段代码:

List<汽车> 一群汽车 = new List<汽车>();
List<车子> 一群车子 = 一群汽车;

显然,上面那段代码是会报错的, 虽然汽车继承于车子,可以隐士转换为车子,但是List<汽车>并不继承于List<车子>,所以上面的转换,是行不通的。

IEnumerable<汽车> 一群汽车 = new List<汽车>();
IEnumerable<车子> 一群车子 = 一群汽车;

然而这样却是可以的。那么IEnumerable接口有什么不同呢,我们且看编译器的提示:

我们可以看到,泛型参数的,用了一个“out”关键字作为声明。看来,关键是这个在起作用了。我们先到这个为止,以上是抛出话题。

接下来我们看一种情况:

当尝试编译下面这个把in泛型参数用作方法返回值的泛型接口时:

public interface IPlayB<in T>
{
T Test();
}

出现了如下编译错误:

错误    1    方差无效: 类型参数“T”必须为“CovarianceAndContravariance.IPlayB<T>.Test()”上有效的 协变式。“T”为 逆变。

我们把in改成out ,就不会提示错误了,为什么会出现这种情况?

3. 自问自答

1)协变、逆变 为什么只能针对泛型接口或者委托?而不能针对泛型类?

      因为它们都只能定义方法成员(接口不能定义字段),而方法成员在创建对象的时候是不涉及到对象内存分配的,所以它们是类型(内存)安全的。

2)协变、逆变 为什么是类型安全的?

本质上是里氏替换原则,由里氏替换原则可知:派生程度小的是派生程度大的子集,所以子类替换父类的位置整个程序功能都不会发生改变。

3)官方对 协变、逆变 的定义现在是否能看懂?

      上面看懂了,官方定义肯定也是没问题的。派生程度小可以理解为基类,派生程度大可以理解为子类或派生类,至于为什么用程度这个词,是因为继承链的深度是没限制的。

4)为什么 in 、out 只能是单向的(输入或输出)?

因为若类型参数同时为输入参数和输出参数,则必然会有一种转换不符合里氏替换原则,即将父类型的变量赋值给子类型的变量,这是不安全的所以需要明确指定 in 或 out。

4、泛型接口类型变体

4.1协变允许方法具有的返回类型比接口的泛型类型参数定义的返回类型的派生程度更大。协变中泛型类型参数只能做 返回值类型,不能做方法的参数类型

不知道啥意思

4.2逆变允许方法具有的实参类型比接口的泛型形参定义的类型的派生程度更小。

不知道啥意思

4.3只有引用类型才支持使用泛型接口中的变体。 值类型不支持变体

IEnumerable<int> integers = new List<int>();
// The following statement generates a compiler error,
.
IEnumerable<Object> objects = integers; // 这个是错误的,因为int是值类型,

 4、逆变和协变的应用

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; namespace Galaxy
{
class Program
{ static void Main(string[] args)
{
Dog aDog = new Dog();
Animal aAnimal = aDog;
List<Dog> lstDogs = new List<Dog>();
// List<Animal> lstAnimal2 = lstDogs;
IMyList<Dog> myDogs = new MyList<Dog>(); IMyList<Animal> myAnimals = myDogs; }
}
public interface IAnimal
{
public void AnimalRun(); }
public abstract class Animal: IAnimal
{
public void AnimalRun()
{ Console.WriteLine("动物再跑");
} }
public class Dog : Animal
{ }
public interface IMyList<out T> { T GetElement(); } public class MyList<T> : IMyList<T> { public T GetElement() { return default(T); } } }

5、变体

如果泛型接口或委托的泛型参数被声明为协变或逆变,该泛型接口或委托则被称为“变体”。

C# 逆变(Contravariance)/协变(Covariance) - 个人的理解的更多相关文章

  1. C#中的协变(Covariance)和逆变(Contravariance)

    摘要 ● 协变和逆变的定义是什么?给我们带来了什么便利?如何应用? ● 对于可变的泛型接口,为什么要区分成协变的和逆变的两种?只要一种不是更方便吗? ● 为什么还有不可变的泛型接口,为什么有的泛型接口 ...

  2. Java中的逆变与协变

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

  3. Java中的逆变与协变(转)

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

  4. Java中的逆变与协变 专题

    结论先行: PECS总结: 要从泛型类取数据时,用extends: 协变 要往泛型类写数据时,用super: 逆变 既要取又要写,就不用通配符(即extends与super都不用) 不变 List&l ...

  5. scala 学习: 逆变和协变

    scala 逆变和协变的概念网上有很多解释, 总结一句话就是 参数是逆变的或者不变的,返回值是协变的或者不变的. 但是为什么是这样的? 协变: 当s 是A的子类, 那么func(s) 是func(A) ...

  6. Scala 深入浅出实战经典 第81讲:Scala中List的构造是的类型约束逆变、协变、下界详解

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-97讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  7. C#4.0新特性(3):变性 Variance(逆变与协变)

    一句话总结:协变让一个粗粒度接口(或委托)可以接收一个更加具体的接口(或委托)作为参数(或返回值):逆变让一个接口(或委托)的参数类型(或返回值)类型更加具体化,也就是参数类型更强,更明确. 通常,协 ...

  8. Java 逆变与协变的名词说明

    最近在研究Thinking in Java的时候,感觉逆变与协变有点绕,特意整理一下,方便后人.我参考于Java中的逆变与协变,但是该作者整理的稍微有点过于概念化,我在这里简单的说一下 我对于协变于逆 ...

  9. C# 逆变与协变

    该文章中使用了较多的 委托delegate和Lambda表达式,如果你并不熟悉这些,请查看我的文章<委托与匿名委托>.<匿名委托与Lambda表达式>以便帮你建立完整的知识体系 ...

随机推荐

  1. thrift基础知识

    1. 架构图 Thrift 包含一个完整的堆栈结构用于构建客户端和服务器端.下图描绘了 Thrift 的整体架构. 图 1. 架构图 如图所示,图中黄色部分是用户实现的业务逻辑,褐色部分是根据 Thr ...

  2. 多线程-线程间通信-多生产者多消费者问题(JDK1.5后Lock,Condition解决办法及开发中代码范例)

    1 package multithread4; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurre ...

  3. JVM垃圾收集器(八)

    一.垃圾收集器 有了前面JVM参数的了解下面来看下JVM的垃圾收集器:如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.  JVM(HotSpot)有7种垃圾收集器,7种垃圾收集 ...

  4. Entity Framework 在OrderBy排序中使用字符串

    public static class LinqExtensions { private static PropertyInfo GetPropertyInfo(Type objType, strin ...

  5. SpringMVC主要组件

    1.DispatcherServlet:前端控制器,接收所有请求(如果配置/,则不包含jsp) 2.handlermapping:判断请求格式,判断希望具体要执行的那个方法 3.HanderAdapt ...

  6. nginx启动失败:Redirecting to /bin/systemctl start nginx.service Failed to start nginx.service: Unit not found.

    解决方法: 是因为nginx没有有添加到系统服务,手动手动添加一个即可. 在 /etc/init.d/下创建名为nginx的启动脚本即可,内容如下: #!/bin/bash # # chkconfig ...

  7. android TextView属性详解

    RelativeLayout用到的一些重要的属性: 第一类:属性值为true或false     android:layout_centerHrizontal  水平居中      android:l ...

  8. 2022寒假集训day5

    day5 五道栈的题加上字符串. 单调队列. T1 表达式括号匹配   洛谷P1739 题目描述 假设一个表达式有英文字母(小写).运算符(+,-,*,/)和左右小(圆)括号构成,以"@&q ...

  9. Kubernets-初见

    只是入门文档. 使用 linux 通过 java -jar 方式部署单体架构,war 包丢tomcat. 使用 Docker部署微服务架构. 使用 K8s Pod 进行部署 一个一个 服务 命令 je ...

  10. Fiddler监听Https请求响应

    Fiddler问题 - creation of the root certificate was not successful 解决办法: http://localhost:8888/    安装证书 ...