用官方的话来讲:协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。
协变:即和谐的变化,自然的变化。因为里氏替换原则父类可以装子类所以子类变父类,比如string变成object感受是和谐的。
逆变:即逆常规的变化,不正常的变化。因为里氏替换原则父类可以装子类但是子类不能装父类所以父类变子类,比如object变成string感受是不和谐的。
协变和逆变的公式为:
协变:IFoo<父类> = IFoo<[子类] 逆变:IBar<子类> = IBar<父类>;
举个例子
协变:
using System; using System.Collections.Generic; class MainClass { static void Main() { IEnumerable<object> list = new List<string>(); } }
逆变:
using System; class MainClass { static void Main() { Action<string> action = new Action<object>((o)=> { }); action(""); } }
协变和逆变是用来修饰泛型的协变:out;逆变:in。在泛型中out修饰泛型称为协变,协变(covariant) 修饰返回值 ,协变的原理是把子类指向父类的关系,拿到泛型中;在泛型中in 修饰泛型称为逆变, 逆变(contravariant )修饰传入参数,逆变的原理是把父类指向子类的关系,拿到泛型中。用于在泛型中修饰泛型字母的,只有泛型接口和泛型委托能使用。
那么协变和逆变的作用是什么呢?
为了防止开发者写出错误的代码,.net 设计者便用了协变和逆变(对应 out 和 in 关键字)来强制要求正确行为。也就是说,即使我们想这么做,一旦我们用了微软提供的 IEnumerable 等接口,我们也无法进行错误的类型转换了。所以归根到底,协变和逆变只是一种约束而已,这种规范限制了我们的泛型接口中要么只能有将类型参数当作返回值的协变相容方法(加了 out 关键字),要么只能有将类型参数当作输入值的逆变相容方法(加了 in 关键字)。