泛型的协变和逆变

C#中泛型可变性的限制

1. 不支持类的类型参数的可变性。只有接口和委托可以拥有可变的类型参数。in  out 修饰符只能用来修饰泛型接口和泛型委托。

2. 可变性只支持引用转换。可变性只能用于引用类型,禁止任何值类型和用户定义的转换,如下面的转换是无效的:

  • 将 IEnumerable<int> 转换为 IEnumerable<object> ——装箱转换
  • 将 IEnumerable<short> 转换为 IEnumerable<int> ——值类型转换
  • 将 IEnumerable<string> 转换为 IEnumerable<XName> ——用户定义的转换

3. 类型参数使用了 out 或者 ref 将禁止可变性。对于泛型类型参数来说,如果要将该类型的实参传给使用 out 或者 ref 关键字的方法,便不允许可变性,如:

delegate void someDelegate<in T>(ref T t)

这段代码编译器会报错。

4. 可变性必须显式指定。从实现上来说编译器完全可以自己判断哪些泛型参数能够逆变和协变,但实际却没有这么做,这是因为C#的开发团队认为:必须由开发者明确的指定可变性,因为这会促使开发者考虑他们的行为将会带来什么后果,从而思考他们的设计是否合理。

5. 多播委托与可变性不能混用。下面的代码能够通过编译,但是在运行时会抛出 ArgumentException 异常:

Func<string> stringFunc = () => "";
Func<object> objectFunc = () => new object();
Func<object> combined = objectFunc + stringFunc;

这是因为负责链接多个委托的 Delegate.Combine方法要求参数必须为相同的类型,而上面的两个泛型委托的输出一个为字符串,另一个为object。上面的示例我们可以修改成如下正确的代码:

Func<string> stringFunc = () => "";
Func<object> defensiveCopy = new Func<object>(stringFunc);
Func<object> objectFunc = () => new object();
Func<object> combined = objectFunc + defensiveCopy;

此时两个泛型委托的输出均为object。

 

协变:从子类转换到父类;泛型参数定义的类型只能作为返回类型,不能作为参数类型;使用out修饰。
逆变:从父类转换到子类;泛型参数定义的类型只能作为参数类型,不能作为返回类型;使用in修饰。

 

posted @ 2022-08-10 14:11  黑魔术师与黑魔术少女  阅读(87)  评论(0)    收藏  举报
1 3 levels of contents