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