泛型接口中的变体 (C#)
.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>();