刘帅的博客

C# 协变和逆变

Word count: 578Reading time: 2 min
2022/11/20

协变和逆变

概念

协变:使用比原始指定的派生类型的派生程度更大(更具体的)的类型

逆变:使用比原始指定的派生类型的派生程度更小(不太具体的)的类型

协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换

协变

假如定义个基类Base,子类:Derived

协变会保留分配兼容性。相当于Base = Derived。

对比上边的概念:使用比原始指定的派生类型(Base)的派生程度更大的类型(Derived)

如:

1
2
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

基类为object,子类为string。

使用比原始指定的派生类型(object)的派生程度更大的类型(string)。换种理解方法,IEnumerable需要的是object类型,但是指定一个派生类string提供给IEnumerable也是满足的。因为符合里氏替换原则,即:父类出现的地方,子类都可以进行替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//自定义协变
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
ICustomCovariant<object> o = new CustomCovariant<string>();
}
}

public interface ICustomCovariant<out T> {
T Get();
}

public class CustomCovariant<T> :ICustomCovariant<T> {
public T Get() {
return default(T);
}
}

}

逆变

逆变不保留分配兼容性。相当于Derived = Base;

对比上边的概念:使用比原始指定的派生类型(Derived)的派生程度更小的类型(Base)

如:

1
2
3
static void SetObject(object o){}
Action<object> actObject = SetObject;
Action<string> actString = actObject;

基类为object,子类为string

使用比原始指定的派生类型(string)的派生程度更小的类型(object)。

换种理解方法:

Action 需要的string类型,但是指定一个object给他。 这样是不兼容的,因为有些string的方法则无法进行使用了,相当于子类的一些方法无法使用了,所以说是不兼容的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//自定义逆变
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
IContravariant<string> 0 = new CustomContravariant<object>();
}
}

public interface IContravariant<in T> {
void Get(T t);
}

public class CustomContravariant<T> : IContravariant<T> {
public void Get(T t) {

}
}
}

参考

https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/covariance-contravariance/

https://blog.51cto.com/u_15296378/4907653