前言:
在引用类型系统时,协变、逆变和不变性具有如下定义。 这些示例假定一个名为 Base 的基类和一个名为 Derived的派生类。
Covariance
使你能够使用比原始指定的类型派生程度更大的类型。
你可以将 IEnumerable
的实例分配给 IEnumerable
类型的变量。
Contravariance
使你能够使用比原始指定的类型更泛型(派生程度更小)的类型。
你可以将 Action
的实例分配给 Action
类型的变量。
Invariance
表示只能使用最初指定的类型。 固定泛型类型参数既不是协变,也不是逆变。
你无法将 List
的实例分配给 List 类型的变量,反之亦然。
以上来自于官方文档对协变、逆变、不变性的解释
为啥C#需要协变和逆变?
我们首先来看一段代码:
class FooBase{ } class Foo : FooBase { } var foo = new Foo(); FooBase fooBase = foo; //以下代码在.NET 4.0之前是不被支持的 IEnumerablefoo = new List (); IEnumerable fooBase = foo;
因此,在这里实际上可以回答,C#的协变和逆变就是主要有两种目的:
- 兼容性:.NET2.0就推出了泛型,而从.NET 2.0到.NET 3.5期间不支持对泛型接口中的占位符T支持隐式转换,因此在.NET4.0推出协变和逆变
- 为了支持更广泛的隐式类型的转换,在这里就是在泛型体系中支持
在C#中,目前只有泛型接口和泛型委托可以支持协变和逆变,
协变(Covariance)
内置的泛型协变接口,IEnumerator
、IQuerable
、IGrouping
:
public interface IEnumerable: IEnumerable { new IEnumerator GetEnumerator(); } public interface IQueryable : IEnumerable , IEnumerable, IQueryable { } public interface IGrouping : IEnumerable , IEnumerable { TKey Key { get; } }
因此这段代码在.NET4.0及以上版本将不会编译报错:
IEnumerable
foo = new List ();
IEnumerablefooBase = foo;
实际上,对于协变,有下面的约束,否则则会在编译时报错:
- 泛型参数占位符以out关键子标识,并且占位符T只能用于只读属性、方法或者委托的返回值,out简而易懂,就是输出的意思
- 当要进行类型转换,占位符T要转换的目标类型也必须是其基类,上述例子则是Foo隐式转为
FooBase
逆变(Contravariance)
内置的泛型逆变委托Action、Func、Predicate,内置的泛型逆变接口IComparable
public delegate void Action(T obj); public delegate TResult Func (T arg); public delegate bool Predicate (T obj); public interface IComparable { int CompareTo(T? other); } public interface IEquatable { bool Equals(T? other); }
而逆变的用法则是这样:
ActionfooBaseAction = new Action ((a)=>Console.WriteLine(a)); Action fooAction = fooBaseAction;
而对于逆变,则跟协变相反,有下面的约束,否则也是编译时报错:
要想标识为逆变,应该是要在占位符T前标识in,只能用于只写属性、方法或者委托的输入参数
当要进行类型转换,占位符T要转换的目标类型也必须是其子类,上述例子则是FooBase
转为Foo
总结#
协变和逆变只对泛型委托和泛型接口有效,对普通的泛型类和泛型方法无效
协变和逆变的类型必须是引用类型,因为值类型不具备继承性,因此类型转换存在不兼容性
泛型接口和泛型委托可同时存在协变和逆变的类型参数,即占位符T
到此这篇关于详析C#的协变和逆变的文章就介绍到这了,更多相关C#的协变和逆变内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!