Общая ковариация и контравариантность

Рассмотрим фрагмент кода.

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);
}

Ответы

Ответ 1

ICollection<T> не является ковариантным по параметру типа, тогда как IEnumerable<T> is. Если вы посмотрите на свои объявления (ICollection, IEnumerable), вы может видеть, что IEnumerable<T> использует ключевое слово out на T, а ICollection<T> - нет.

Это имеет смысл, если вы думаете об этом, поскольку (грубо говоря) ковариация безопасна, когда интерфейс будет использоваться только для чтения объектов (и, следовательно, ключевое слово out). IEnumerable<T> явно соответствует этому критерию, тогда как ICollection<T> является совершенно противоположным.

В качестве примера того, что может пойти не так (используя ваш пример):

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

Помните: ковариация - это не то же самое, что наследование. Просто потому, что два класса или интерфейсы разделяют отношение наследования, не означает, что их параметры типа имеют одни и те же характеристики дисперсии.

Ответ 2

Ключевым моментом является то, может ли коллекция быть модифицируемой. IEnumerable<T> представляет собой коллекцию только для чтения T s, тогда как ICollection<T> поддерживает Add. Модифицируемая коллекция не может быть ковариантной, потому что:

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

Это будет проверка типа, поскольку (предположительно) Elephant является подклассом Object. Но теперь obj, который является List<string>, имеет в качестве последнего элемента Elephant, что явно плохо.