逆变(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. vue学习14-自定义组件添加属性

    <!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta http ...

  2. 0,NULL和nullpter

    #include <iostream> using namespace std; void f(int) { cout<<"f(int)"<<e ...

  3. golang中字符串的底层实现原理和常见功能

    1. 字符串的底层实现原理 package main import ( "fmt" "strconv" "unicode/utf8" ) f ...

  4. Understanding C++ Modules In C++20 (2)

    Compiling evironment: linux (ubuntu 16.04)+ gcc-10.2. The post will focus on using export,import,vis ...

  5. 【JS】函数提升变量提升以及函数声明和函数表达式的区别

    今天看js的变量提升问题,里面提到了函数提升.然后发现自己之前一直把函数声明和函数表达式弄错,导致函数提升出错 一.变量提升 console.log(a) var a=100 //undefined ...

  6. 接口里的default,static方法

    我们都知道接口里的变量默认隐含类型是public static final,也是就是说是常量.而方法默认类型是public abstract,所以接口的方法都是抽象方法,但是事实真的是这样吗? 我的P ...

  7. 【linux运维】Linux服务器玩转vsftpd安装与配置

    Linux服务器搭建ftp详细教程. 前言 本文,主要以vsftp软件为主进行讲解,版本为3.0.2.以介绍设置虚拟用户.权限验证为主,linux权限知识默认大家都有所了解.vsftpd设置有两种模式 ...

  8. latex知识点

    sensors期刊下载的laTex模板错误修改 把上面的那个删了,原因是什么那,请看下文...... 如何使用endnote + latex 1.使用word将文献按照word引用顺序导出成一个新的e ...

  9. 用maven创建项目引入js文件位置

    用maven创建项目引入js文件位置 截图如下:

  10. centos安装MySQL问题

    使用sudo yum install mysql-server出现没有可用软件包 mysql-server. 先 执行 wget http://repo.mysql.com/mysql-communi ...