记录 官方文档的协变与逆变学习过程。

使用举例

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

1、委托类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
Func<Bird> birdFunc = () => new Bird();
Func<Animal> animalFunc = () => new Animal();
animalFunc = birdFunc;
//协变 Func参数使用了out关键字
Animal animal = animalFunc();

Action<Animal> animalAction = (t) => { };
Action<Bird> birdAction = animalAction;
//逆变 Action参数使用了in关键字
birdAction(new Bird());
}
}
class Animal { }
class Bird : Animal { }
}

2、泛型接口和数组类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
IEnumerable<Bird> birds = new List<Bird>() { new Bird() { } };
IEnumerable<Animal> animals = birds; //协变 IEnumerable<out T>
var enumerators = animals.GetEnumerator();
enumerators.MoveNext();
Console.WriteLine(enumerators.Current.GetType().Name); //Bird

Animal[] animalArray = new Bird[3];
animalArray[0] = new Bird();
Console.WriteLine(animalArray[0].GetType().Name); //Bird

//System.ArrayTypeMismatchException:“Attempted to access an element as a type incompatible with the array.”
animalArray[1] = new Animal();
}
}
class Animal { }
class Bird : Animal { }
}

如果泛型接口或委托的泛型参数被声明为协变或逆变,该泛型接口或委托则被称为“变体”。

错误示范:

1
2
3
4
5
 List<Object> list = new List<string>(); 
//实现变体接口的类仍是固定类,这样是无法转换的。

IEnumerable<int> integers = new List<int>();
IEnumerable<Object> objects = integers; //只能用于引用类型
创建变体泛型接口

通过对泛型类型参数使用out关键字,将参数声明为协变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface ICovariant<out R>
{
R GetSomething();
void DoSomething(Action<R> callback); //逆变参数
}

class Implementation<R> : ICovariant<R>
{
public void DoSomething(Action<R> callback)
{
throw new NotImplementedException();
}

public R GetSomething()
{
throw new NotImplementedException();
}
}

通过对泛型类型参数使用in关键字,将参数声明为逆变。

1
2
3
4
5
interface IContravariant<in A>
{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A; //逆变参数可以使用约束,协变不可以
}

同一个接口,可以同时有逆变参数和协变参数,例如Func<in T,out TResult>

派生变体泛型接口

派生变体泛型接口,仍需使用in、out关键字来显示指定是否支持变体。

1
2
3
4
5
6
7
8
9
10
11
interface ICovariant<out R>
{
R GetSomething();
void DoSomething(Action<R> callback); //逆变参数
}
interface IExtCovariant<out R> : ICovariant<R> //协变参数
{
}
interface IExtCovariantOne<R> : ICovariant<R> //固定参数
{
}

如果父接口参数声明为逆变,则派生接口只能和父相同,或者声明为固定参数。