逆变(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. 第02讲:Flink 入门程序 WordCount 和 SQL 实现

    我们右键运行时相当于在本地启动了一个单机版本.生产中都是集群环境,并且是高可用的,生产上提交任务需要用到flink run 命令,指定必要的参数. 本课时我们主要介绍 Flink 的入门程序以及 SQ ...

  2. lua之自索引

    Father={ a=100, b=200 } function Father:dis() print(self.a,self.b) end Father.__index=Father Son= { ...

  3. gorm声明模型

    模型定义 模型是标准的结构体,由go的基本数据类型.实现了Scanner和Valuer接口的自定义类型及其指针或别名组成 例如: type User struct { ID uint Name str ...

  4. golang中的tcp编程

    1. tcp server package main import ( "bufio" "fmt" "net" ) func main() ...

  5. Go 面向对象编程应用

    #### Go 面向对象编程应用前面学习了很多的基础知识,这一节来实际写一个小案例:涉及到的知识: 1. 数组的基本使用2. 结构体3. 切片 4. 方法5. 循环6. 函数返回值(命名返回值,普通返 ...

  6. Go 结构体方法

    #### Go 结构体方法本来今天有些事情忙的不准备更新内容了,后来提前完成了, 所以还是要更新了; 毕竟坚持本就是一件不容易的事情!加油,相信不管是大家还是我,都有一些事情想要做,那就坚持吧,剩下的 ...

  7. 集合框架-Map集合-LinkedHashMap及关联源码操作

    1 package cn.itcast.p9.linkedhashmap.demo; 2 3 import java.util.HashMap; 4 import java.util.Iterator ...

  8. 使用AJAX请求调用出现HTTPS协议错误问题

    前言: 这又是一个可能是半路就卡机的项目,在调用ajax的时候遇到了下面的这个错. js中有个ajax请求http,url是:http//:.js就提示请求了一个不安全的脚本,在发送ajax请求时,就 ...

  9. coredns 安装

    coredns简介 CoreDNS是一个DNS服务器,和Caddy Server具有相同的模型:它链接插件.CoreDNS是云本土计算基金会启动阶段项目.CoreDNS是SkyDNS的继任者. Sky ...

  10. HTML 基础2

    当浏览器读到一个样式表,它就会按照这个样式表来对文档进行格式化.有以下三种方式来插入样式表: 外部样式表 内部样式 内联样式 外部样式表 当样式需要被应用到很多页面的时候,外部样式表将是理想的选择.使 ...