一般协方差和逆变

本文关键字:方差 | 更新日期: 2023-09-27 18:02:11

考虑以下代码片段:

IList<String> obj=new List<string>();
IEnumerable<Object> obj1 = obj;

但是如果我写ICollection<Object> obj2 = obj;,它会抛出一个编译时错误。

不能隐式地将类型' System.Collections.Generic.IList<string> '转换为' System.Collections.Generic.ICollection<object> '。

既然List<T>实现了IEnumerable<T>ICollection<T>,并且IList<T>被定义为

,为什么会出现这种行为?
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
    T this[int index] { get; set; }
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);
}

一般协方差和逆变

ICollection<T>在类型参数上不是协变的,而IEnumerable<T>是。如果你看一下它们的声明(ICollection, IEnumerable),你可以看到IEnumerable<T>T上使用了out关键字,而ICollection<T>没有。

如果你仔细想想,这是有意义的,因为(粗略地说)协方差在接口只用于读取对象(因此out关键字)时是安全的。IEnumerable<T>显然符合这个标准,而ICollection<T>则完全相反。

作为可能出错的示例(使用您的示例):

IList<String> obj = new List<string>(); // Legal, of course
ICollection<Object> obj1 = obj;         // Illegal, but let's see what happens
obj1.Add(new NonStringObject());        // That's not a string being stored in a List<string>

记住:协方差不同于继承。仅仅因为两个类或接口共享继承关系并不意味着它们的类型参数共享相同的方差特征。

这里的关键是集合是否可修改。IEnumerable<T>T s的只读集合,而ICollection<T>支持Add。可修改集合不能协变,因为:

IList<String> obj = new List<String>();
ICollection<Object> obj1 = obj;
obj1.Add(new Elephant());

这将进行类型检查,因为(假设)ElephantObject的子类。但是现在obj,它是一个List<string>,它的最后一个元素是Elephant,这显然是一件坏事。