码迷,mamicode.com
首页 > Windows程序 > 详细

C#协变和逆变

时间:2020-04-05 13:37:19      阅读:89      评论:0      收藏:0      [点我收藏+]

标签:guid   each   for   mamicode   接口   guide   nim   ros   代码   

本篇博客所讲的是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类多了,得看书,查资料。

 

C#协变和逆变

标签:guid   each   for   mamicode   接口   guide   nim   ros   代码   

原文地址:https://www.cnblogs.com/aqgy12138/p/12636735.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!