微软官方概述:
在C#中,协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。协变保留分配兼容性,逆变则与之相反。
协变:能够使用与原始指定的派生类型相比,派生程度更大的类型。
逆变:能够使用派生程度更小的类型。
官方示例:
string str = "test";
object obj = str;
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;
static void SetObject(object){}
Action<object> actObject = SetObject;
Action<string> actString = actObject;
上面示例中,从 string --> object 的隐式转换这是协变;如果从 object --> string 转换这是逆变。
数组协变
string[] str = new string[10];
object[] array = str;
这是一种不安全的操作,在对 array数组赋值时,array[0] = 0; 在编译时可以通过,但是在运行时会提示错误。“System.ArrayTypeMismatchException:“尝试访问类型与数组不兼容的元素。””。这里的array 只是保存了str的引用,仍然是一个string类型的数组。
委托中的协变和逆变
对方法组的协变和逆变支持将方法签名与委托类型相匹配。这样不仅可以将具有匹配签名的方法分配给委托,还可以分配与委托类型指定的派生类型相比,返回派生程度更大的类型的方法(协变)或者如果方法所接受参数的派生类型所具有的程度小于委托类型指定的程度(逆变),也可将其分配给委托。这就包含泛型委托和非泛型委托。
class People {}
class Student:People{}
delegate People Method();
class Program
{
People peopleMethod(){}
Student studetnMethod(){}
static void Test(){
Method method = peopleMethod;
Method method2 = studentMethod;
}
}
上面的委托Method(),返回值类型是People,但是我们采用更加具体的student类型也是可以的,由student --> People类型的转换,这是正常的多态行为。委托的目标方法可能返回比委托声明的返回值类型更加特定的返回值类型,这称为协变。
那么当我们也可以采用一个比目标方法参数类型更加具体的参数类型,这称为逆变。
下面的示例:
delegate void StringAction (string s);
class Test{
static void Main(){
StringAction sa = new StringAction (ActionObject);
sa("hi");
}
static void ActionObject(object o) => Console.WriteLine(o);
}
C# 4.0添加了 out 和 in 关键字,来分别支持协变和逆变。
泛型中的协变和逆变,单独介绍