Общая ковариация и контравариантность
Рассмотрим фрагмент кода.
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
, что явно плохо.