zoukankan      html  css  js  c++  java
  • C#协变和逆变

    本篇博客所讲的是C#泛型中的协变和逆变。

    首先讲协变:

    协变

    要把泛型参数定义为协变,可在类型定义中使用out关键字,例如:

     public interface IEnumerable<out T> : IEnumerable
     {
            IEnumerator<T> GetEnumerator();
     }

    相信这个方法,大伙都知道,这个是C#中内置的一个IEnumerable<T>接口。

    假定A可以转化为B,如果X<A>可以转为为B,那么称X有一个协变类型参数。

              {
    
                    IEnumerable<string> str = new List<string> { "Zero", "One", "Two" };
                    IEnumerable<object> obj = str;
    
                    foreach (var item in obj)
                    {
                        Console.WriteLine(item);
                    }
                }

    比如这段代码,它能不能执行呢?能的,执行结果如图:

     如果我把IEnumerable<object> obj换成IEnumerable<int> obj,上面这段代码肯定是没法跑的了,这就衍生出一个问题,如果两个类没有父子关系,它们是没有办法协变的,

    根据上面的例子,我们知道C#中Object是最底层的基类,那么string类型是继承object类型的,通过协变IEnumerable<string>是能够隐式转为IEnumerable<object>。

    C#中有内置的IEnumerable<T>,那我们如何自己在日常开发中用到协变呢。

    }
    public class Cat : Animal
    {

    }

     /// <summary>
        /// 定义协变
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public interface IAnimal<out T>:IAnimal
        {
        
        }
        /// <summary>
        /// 抽象动物
        /// </summary>
        public interface IAnimal
        {
            void Name(Animal animal);
        }
        /// <summary>
        /// 具体的动物
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class ConceteAnimal<T> : IAnimal<T>
        {
            public void Name(Animal animal)
            {
                Console.WriteLine($"这是{animal.Name}");
            }
        }
        //定义动物基类
        public class Animal
        {
            public string Name { get; set; }
        }
        public class Dog : Animal
        {
    
        }
        public class Cat : Animal
        {
    
        }

    首先定义一个抽象的动物,然后定义一个协变接口,定义一个具体的动物实现抽象方法。

    然后定义个一只猫,一只狗。

     ConceteAnimal<Cat> cat = new ConceteAnimal<Cat>();
     cat.Name(new Cat { Name = "" });
     IAnimal<Animal> animal1 = cat;

    当具体动物是一只猫的时候,执行方法。

    通过手写了一个例子,其实能够发现这个东西还是蛮好用的,但是不是很好理解,需要多去敲代码,才能体会到其中的奥妙。

    协变的好处是能够很好的解决复用性的问题。协变仅仅对引用转换有效,对装箱是无效的。

    协变在接口(interface)中是比较常见的,但是这个东西需要跟方法中的out参数是要做区分的,方法中的out参数是不支持协变的,这是CLR的限制。

    逆变

    其实这个也还能叫抗变,我看了《C#入门经典第七版》是叫抗变的,目前官方文档的叫法是逆变(https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/covariance-contravariance/),《C#图解教程》中泛型那一章也是叫逆变的,所有标题就叫逆变好了。

    通过协变我们知道,假定A可以转化为B,如果X<A>可以转为为B,那么称X有一个协变类型参数。协变正好相反,即,从X<B>转换为X<A>,它仅在类型参数出现在输入位置上,并且用in修饰符才能行。

    下面上例子。

    /// <summary>
        /// 定义逆变
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public interface IAnimal<in T> : IAnimal
        {
    
        }

    把out修饰符换成in修饰符。

     ConceteAnimal<Animal> animal = new ConceteAnimal<Animal>();
    animal.Name(new Animal { Name = "" });
    IAnimal<Dog> dog = animal;

    执行代码。

     通过上面的协变,再来看这个逆变,其实就是倒过来的,我在实际学习中是也是跟博客一样先学习的协变再学逆变,学逆变的时候感觉这个东西还是蛮通透的。

    在c#中也是内置了一些逆变interface的,例如:IComparer<in T>,在这我就不去实现它了。

    c#中的协变和逆变,都是基于泛型,所以例如泛型委托,也是会用到协变和逆变的,这个如果以后写委托的博客的时候再去写一下与之相关的。

    写这种纯C#的知识博客,比写记录bug类多了,得看书,查资料。

  • 相关阅读:
    springBoot 2.1.5 pom 文件 unknown 错误
    @HystrixCommand 不能被导包
    SQL数据库连接语句
    ADO.NET中COMMAND对象的ExecuteNonQuery、ExcuteReader和ExecuteScalar方法
    重载和重写的区别
    抽象类和接口的相同点和不同点
    结构详解
    简单工厂和抽象工厂的区别
    DataRead和DataSet的异同
    什么是Web Server
  • 原文地址:https://www.cnblogs.com/aqgy12138/p/12636735.html
Copyright © 2011-2022 走看看