.NET Framework 4 引入了对多个现有泛型接口的方差支持。 变体支持允许实现这些接口的类进行隐式转换。
从 .NET Framework 4 开始,以下接口是变体:
IEnumerable<T> (T 为协变)
IEnumerator<T> (T 为协变)
IQueryable<T> (T 为协变)
IGrouping<TKey,TElement> (
TKey
和TElement
是协变的)IComparer<T> (T 是逆变)
IEqualityComparer<T> (T 是逆变)
IComparable<T> (T 是逆变)
从 .NET Framework 4.5 开始,以下接口是变体:
IReadOnlyList<T> (T 为协变)
IReadOnlyCollection<T> (T 为协变)
协变允许方法具有比接口的泛型类型参数定义的更派生的返回类型。 为了说明协变功能,请考虑以下泛型接口: IEnumerable<Object>
和 IEnumerable<String>
。 该 IEnumerable<String>
接口不继承 IEnumerable<Object>
接口。 但是,该 String
类型确实继承了该 Object
类型,在某些情况下,你可能希望向彼此分配这些接口的对象。 以下代码示例显示了此操作。
IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;
在 .NET Framework 的早期版本中,此代码会导致 C# 中的编译错误,如果 Option Strict
处于打开状态,则为 Visual Basic。 但现在可以使用 strings
,而不是 objects
如前面的示例所示,因为 IEnumerable<T> 接口是协变的。
逆变允许方法具有的实参类型比接口的泛型形参定义的类型的派生程度更小。 为了说明逆变,假设已创建一个 BaseComparer
类来比较类的 BaseClass
实例。
BaseComparer
类实现 IEqualityComparer<BaseClass>
接口。 由于IEqualityComparer<T>接口现在是逆变的,因此可以使用BaseComparer
来比较继承自BaseClass
类的那些类的实例。 以下代码示例显示了此操作。
// Simple hierarchy of classes.
class BaseClass { }
class DerivedClass : BaseClass { }
// Comparer class.
class BaseComparer : IEqualityComparer<BaseClass>
{
public int GetHashCode(BaseClass baseInstance)
{
return baseInstance.GetHashCode();
}
public bool Equals(BaseClass x, BaseClass y)
{
return x == y;
}
}
class Program
{
static void Test()
{
IEqualityComparer<BaseClass> baseComparer = new BaseComparer();
// Implicit conversion of IEqualityComparer<BaseClass> to
// IEqualityComparer<DerivedClass>.
IEqualityComparer<DerivedClass> childComparer = baseComparer;
}
}
有关更多示例,请参阅在泛型集合的接口中使用变体(C#)。
只有引用类型才支持使用泛型接口中的变体。 值类型不支持差异。 例如, IEnumerable<int>
无法隐式转换为 IEnumerable<object>
整数,因为整数由值类型表示。
IEnumerable<int> integers = new List<int>();
// The following statement generates a compiler error,
// because int is a value type.
// IEnumerable<Object> objects = integers;
还请务必记住,实现变体接口的类仍然固定。 例如,虽然List<T>实现协变接口IEnumerable<T>,但不能隐式转换为List<String>
List<Object>
。 下面的代码示例对此进行了说明。
// The following line generates a compiler error
// because classes are invariant.
// List<Object> list = new List<String>();
// You can use the interface object instead.
IEnumerable<Object> listObjects = new List<String>();