• 从C# 4.0开始,泛型接口和泛型委托都支持协变和逆变,由于历史原因,数组也支持协变。
  • 里氏替换原则:任何基类可以出现的地方,子类一定可以出现。

协变(out)

  • 协变:即自然的变化,遵循里氏替换原则,表现在代码上则是任何基类都可以被其子类赋值,如Animal = Dog、Animal = Cat
  • 使用out关键字声明(注意和方法中修饰参数的out含义不同)
  • 被标记的参数类型只能作为方法的返回值(包括只读属性)
  • 在没有协变时:
    abstract class Animal {}
    class Dog : Animal {}
    class Cat : Animal {} interface IPoppable<T>
    {
    T Pop();
    }
    class MyStack<T> : IPoppable<T>
    {
    private int _pos;
    private readonly T[] _data = new T[100]; public void Push(T obj) => _data[_pos++] = obj;
    public T Pop() => _data[--_pos];
    }

    以下代码是无法通过编译的

    var dogs = new MyStack<Dog>();
    IPoppable<Animal> animals1 = dogs; // 此处会发生编译错误
    Stack<Animal> animals2 = dogs; // 此处会发生编译错误

    此时,我们如果需要为动物园饲养员新增一个输入参数为Stack<Animal>饲喂的方法,一个比较好的方法是新增一个约束泛型方法:

    class Zookeeper
    {
    public static void Feed<T>(IPoppable<T> animals) where T : Animal {}
    }
    // 或者
    class Zookeeper
    {
    public static void Feed<T>(Stack<T> animals) where T : Animal {}
    } // Main
    Zookeeper.Feed(dogs);
  • 现在,C#增加了协变

    使IPoppable<T>接口支持协变
    // 仅仅增加了一个 out 声明
    interface IPoppable<out T>
    {
    T Pop();
    }

    简化Feed方法

    class Zookeeper
    {
    public static void Feed(IPoppable<Animal> animals) {}
    } // Main
    Zookeeper.Feed(dogs);

    协变的天然特性——仅可作为方法返回值,接口(或委托)外部无法进行元素添加,确保了泛型类型安全性,所以不用担心Dog的集合中出现Cat

  • 常用的支持协变的接口和委托有:
    • IEnumerable
    • IEnumerator
    • IQueryable
    • IGrouping<out TKey, out TElement>
    • Func等共17个
    • Converter<in TInput, out TOutput>
    IEnumerable<Dog> dogs = Enumerable.Empty<Dog>();
    IEnumerable<Animal> animals = dogs; var dogList = new List<Dog>();
    IEnumerable<Animal> animals = dogList;
  • 另外,由于历史原因,数组也支持协变,例如
    var dogs = new Dog[10];
    Animal[] animals = dogs;

    但是无法保证类型安全性,以下代码可正常进行编译,但是运行时会报错

    animals[0] = new Cat(); // 运行时会报错

逆变(in)

  • 逆变:即协变的逆向变化,实质上还是遵循里氏替换的原则,将子类赋值到基类上
  • 使用in关键字声明
  • 被标记的参数类型只能作为方法输入参数(包括只写属性)
  • 例如:
    abstract class Animal {}
    class Dog : Animal {}
    class Cat : Animal {} interface IPushable<in T>
    {
    void Push(T obj);
    }
    class MyStack<T> : IPushable<T>
    {
    private int _pos;
    private readonly T[] _data = new T[100]; public void Push(T obj) => _data[_pos++] = obj;
    public T Pop() => _data[--_pos];
    } // Main
    var animals = new MyStack<Animal>();
    animals.Push(new Cat());
    IPushable<Dog> dogs = animals;
    dogs.Push(new Dog());

    逆变的天然特性——仅可作为方法输入参数,接口(或委托)无法进行元素获取,即只能将子类赋值到父类上,进而保证了类型安全性。

  • 另外,常用支持逆变的接口和委托有:
    • IComparer
    • IComparable
    • IEqualityComparer
    • Action等共16个
    • Predicate
    • Comparison
    • Converter<in TInput, out TOutput>
Action<Animal> animalAction = new Action<Animal>(a => { });
Action<Dog> DogAction = animalAction;

你了解C#的协变和逆变吗的更多相关文章

  1. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  2. C#协变和逆变

    我们知道在C#中,是可以将派生类的实例赋值给基类对象的.

  3. C# 泛型的协变和逆变

    1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...

  4. 不变(Invariant), 协变(Covarinat), 逆变(Contravariant) : 一个程序猿进化的故事

    阿袁工作的第1天: 不变(Invariant), 协变(Covarinat), 逆变(Contravariant)的初次约 阿袁,早!开始工作吧. 阿袁在笔记上写下今天工作清单: 实现一个scala类 ...

  5. 再谈对协变和逆变的理解(Updated)

    去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...

  6. 【转】c# 协变和逆变

    本文转自:http://www.cnblogs.com/rr163/p/4047404.html C#的协变和逆变 由子类向父类方向转变是协变,用out关键字标识,由父类向子类方向转变是逆变,用in关 ...

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

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

  8. 深入理解 C# 协变和逆变

    msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样,不过不够直白. 直白的理解: “协变” ...

  9. Java用通配符 获得泛型的协变和逆变

    Java对应泛型的协变和逆变

  10. [改善Java代码]警惕泛型是不能协变和逆变的

    什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是 ...

随机推荐

  1. Codeforces 1340B Nastya and Scoreboard(dp,贪心)

    题目链接OvO 题目大意   给你\(n\)串数字,\(1\)代表该位置是亮的,\(0\)代表是灭的.你必须修改\(k\)个数字,使某些\(0\)变为\(1\).注意,只能把原来的\(0\)改成\(1 ...

  2. 一个老牌程序员推荐的JavaScript的书籍,看了真的不后悔!

    很多人问我怎么学前端?我的回答是:读书吧!相对于在网上学习,在项目中学习和跟着有经验的同事学习,书中有着相对完整的知识体系,每读一本好书都会带来一次全面的提高.而如果深一脚浅一脚的学习,写出代码的质量 ...

  3. /sbin/mount.vboxsf: mounting failed with the error: Protocol error

    公司换了新电脑,需要把之前的虚拟机的配置全部备份下来,在移动的过程中挂载共享文件夹时候出现了 /sbin/mount.vboxsf: mounting failed with the error: P ...

  4. 14.在Python中lambda函数是什么

    在Python中lambda函数是什么? It is a single expression anoymous function often used as inline function. lamb ...

  5. mysql硬件优化

    导致宕机的原因: 1.      运行环境: 35%,运行环境可以看做是支持数据库服务器运行的系统和资源集合,包括操作系统,硬盘以及网络 2.      性能:35% 3.      复制 20% 4 ...

  6. POJ2155/LNSYOJ113 Matrix【二维树状数组+差分】【做题报告】

    这道题是一个二维树状数组,思路十分神奇,其实还是挺水的 题目描述 给定一个N∗NN∗N的矩阵AA,其中矩阵中的元素只有0或者1,其中A[i,j]A[i,j]表示矩阵的第i行和第j列(1≤i,j≤N)( ...

  7. 全网最清晰的ConstraintLayout教程

    ConstraintLayout是AndroidStudio2.2新增的一个功能,那么这个到底是什么呢?首先第一点我们知道传统的安卓开发,页面基本都是XML编写实现,特别在一些复杂的页面上需要嵌套多层 ...

  8. VS中的生成和重新生成的区别

    2019独角兽企业重金招聘Python工程师标准>>> 生成 在上次编译的基础上,只对改动过的文件重新生成,没有改动过的文件不会重新生成. 重新生成 对所有的文件都重新生成.如果引用 ...

  9. bfs—Dungeon Master—poj2251

    Dungeon Master Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 32228   Accepted: 12378 ...

  10. 数学--数论--HDU 12151七夕节

    七夕节 Problem Description 七夕节那天,月老来到数字王国,他在城门上贴了一张告示,并且和数字王国的人们说:"你们想知道你们的另一半是谁吗?那就按照告示上的方法去找吧!&q ...