在看C#的时候看到了协变与逆变,百度一下发现原来C++, java里早就有了协变与逆变.
首先说明协变与逆变的含义:
逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)
f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;
f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;
f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
上述内容网上很多博客都有介绍,并且从上面的描述来看,对于某一个参数来说逆变,协变,不变只能选其一,不能既是协变又是逆变,因为
fA)<=f(B)f时不会有(B)<=f(A)。
为了详细说明协变与逆变,介绍下里氏替换原则:
所有引用基类的地方必须能透明地使用其子类的对象。也就是说基类引用(或指针)可以指向子类对象。
C++ Primer中有:从派生类向基类的类型转换只对指针或引用类型有效,说的就是里氏替换原则。
为了满足里氏替换原则于是就有了协变与逆变。在博客 https://www.cnblogs.com/pyes/p/4907776.html 看到的这句话(当然有可能是书上的)觉得很好的解释了协变逆变的作用:
参数逆变:正是因为需要符合里氏替换法则,方法中的参数类型声明时必须符合逆变(或不变),以让子类方法可以接收更大的范围的参数(处理能力增强);而不能声明为协变,子类方法可接收的范围是父类中参数类型的子集(处理能力减弱)。
返回值协变:如果结果类型是逆变的,那子类方法的处理能力是减弱的,不符合里氏替换。因此返回值类型声明时必须符合协变(或不变)。
现在看下c++和C#中的协变与逆变。
对于C++里的指针和引用显然是符合协变的,而为了让模板协变,在C++里需要提供特殊的复制构造函数和特殊的重载赋值运算符,逆变则是通过std::function实现,具体参考博客:https://www.jianshu.com/p/db76a8b08694,比较复杂。
C#里则有关键字out, in,手动实现比较简单,比如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestCovInvert
{
class Base{ }
class Derived : Base { }
class Program
{
delegate void MyFunc_cov < in T >( T obj );//逆变
delegate T MyFunc_invert();
static void Main(string[] args)
{
MyFunc_cov myFunc_Cov_Base = ( b ) => { };
MyFunc_cov myFunc_Cov_Derived;
myFunc_Cov_Derived = myFunc_Cov_Base;
MyFunc_invert myFunc_Invert_Base;
MyFunc_invert myFunc_Invert_Derived = () => { return new Derived(); };
myFunc_Invert_Base = myFunc_Invert_Derived;
}
}
}
out 关键字指定该类型参数是协变的,让是返回值保留继承关系,所以上面代码中测试协变的委托是这样的:
delegate T MyFunc_invert
类型参数用out修饰,并且这里函数返回值类型为T,正是因为out关键字是为了让返回值保留继承关系所以叫out啊。
因为Derived继承于Base,使用了out保留了继承关系所以有 myFunc_Invert_Base = myFunc_Invert_Derived;
in关键字指定该类型参数是逆变的,反转了继承关系,A
delegate void MyFunc_cov < in T >( T obj );//逆变
类型参数使用in修饰, 返回类型绝对不能是T,否则报错, 差异无效: 类型参数“T”必须是在“Program.MyFunc_cov
因为Derived
参考:
https://www.jianshu.com/p/db76a8b08694
https://www.cnblogs.com/zhaopei/p/variability.html