Почему IEnumerable<T> имеет только ковариантный флаг "out", а не "in" C#?

Мне было интересно, почему IEnumerable<T> имеет только out а не in контрвариантный флаг?

public interface IEnumerable<out T>

Я могу понять out на этом примере:

IEnumerable<string> strings = new List<string>(){"a","b","c"};
IEnumerable<object> objects = strings;

object больше чем string, так что компилятор боится, что хорошо сделать что-то вроде:

 objects = objects.First().Select(x=>5);// ( we cant insert int to string...)

хорошо и понятно.

но что делать, если я хочу использовать IEnumerable в качестве вставки?

что-то вроде:

IEnumerable<object> objects = ...;
IEnumerable<string> strings = objects

так что я могу также вставлять в объекты...

но проблема в том, что нет IEnumerable<in T>...

я что-то здесь упускаю?

6 ответов

Решение

Вы не можете вставить в IEnumerable, К сожалению, нет "IInsertable".

Нет интерфейса, сообщающего, что вы можете добавлять элементы только в коллекцию. Это было бы in универсальный тип. Поскольку вы можете получить элементы на каждом интерфейсе коллекции, невозможно in общие аргументы.

редактировать

Интерфейс IEnumerable<T> имеет только геттеры, поэтому вы не можете вставлять какие-либо элементы в коллекцию, используя этот интерфейс. Из-за этого, out разрешено в декларации IEnumerable<out T>, Он сообщает компилятору, что значения типа T только в одном направлении, "выход". Это делает его совместимым в одном направлении. Ты можешь использовать IEnumerable<string>как IEnumerable<object>потому что вы только читаете из него. Вы можете прочитать строку как объект. Но вы не могли писать в него, вы не могли передать объект в виде строки. Это может быть целое число или любой другой тип.

У Эрика Липперта есть серия постов по этому вопросу до введения в C# контравариантности, которую стоит прочитать.

простой пример будет

public class Animal(){
...
}

public class Giraf(){
   public void LookAboveTheClouds(){}
}

IEnumerable<Animal> animals = new list<Animal>{
                                    new Elephant(),
                                    new Tiger(), 
                                    new TinyMouse()};
IEnumerable<Giraf> girafs = animals;

foreach(var giraf in girafs){
   //elephants,tigers and tiny mice does not suuport this
   giraf.LookAboveTheClouds() 
}

другими словами, компилятор не может гарантировать, что вы можете безопасно использовать свой IEnumerable<Animal> как IEnumerable<Giraf>, Все жирафы - животные, но не все животные - жирафы

Я не вижу твоей проблемы здесь. Когда используешь IEnumerable Вы не можете вставить, и это не то, что вы делаете. То, что вы пытаетесь сделать, это назначить IEnumerable<string> значение IEnumerable<object> и это не сработает. Когда вы назначаете IEnumerable<string> с объектом, который имеет тип IEnumerble<object> Вы не можете гарантировать, что все объекты в списке имеют тип string. Вот почему не могу этого сделать.

Ковариант out ключевое слово interfaces гарантирует, что выходные данные всегда могут быть поняты потребляющей частью, то есть IEnumerable<string> может быть приведен к списку IEnumerable<object> поскольку string : object и кто-то потребляет список object должен знать, как обращаться с string но не наоборот.

Контравариант, in ключевое слово interfaces позволяет вам приводить объекты к менее определенным родовым объектам, то есть IComparer<object> может быть приведен к IComparer<string> так как класс, реализующий IComparer<object> теперь надо как обращаться string поскольку это противоречиво, но не наоборот.

Подробнее читайте здесь: http://msdn.microsoft.com/en-us/library/dd799517.aspx

Я думаю, что строго соблюдается тип безопасности: что делать, если вы в своем списке
IEnumerable<object> {"Hello", 2.34d, new MyCoolObject(), enumvalue}?

Как время выполнения должно относиться к этим преобразованиям?

В случае string это может быть простой случай, потому что этого должно быть достаточно, чтобы вызвать переопределение (если есть) ToString() Но разработчик фреймворка не может полагаться на пользовательские случаи и должен предоставлять общие решения.

Надеюсь это поможет.

Как уже упоминалось в комментариях, вы не можете использовать IEnumerable для изменения коллекции.

Компилятор не допустит код, подобный приведенному ниже, из-за проблем безопасности типов, как тот, который вы упомянули (вставка int в список строк), потому что вы можете изменять здесь списки строк, используя объекты

IEnumerable - это просто итератор над коллекцией.

static void Main(string[] args)
{
    IList<string> strings = new List<string> { "a", "b", "c" };
    IList<object> objects = strings;  ** // This will give an error because compiler does not want you to do the line below **
    objects.Add(5);  // Type safety violated for IList<string> hence not allowed


    // The code below works fine as you cannot modify the strings collection using IEnumerable of object
    //IEnumerable<string> strings = new List<string> { "a", "b", "c" };
    //IEnumerable<object> objects = strings;
}

Надеюсь это поможет.

Вы не можете назначить IEnumerable<Parent> в IEnumerable<Child> потому что он может содержать элементы, которые не являютсяChild,

но что делать, если я хочу использовать IEnumerable в качестве вставки? что-то вроде:

IEnumerable<object> objects = ...;
IEnumerable<string> strings = objects

Во время компиляции невозможно узнать, что IEnumerable<object> не содержит неstring, Однако, если вы (программист) знаете, что есть только strings там, вы можете делать кастинг во время выполнения:

IEnumerable<string> strings = objects.Cast<string>();

Кстати, вы можете посмотреть описание ковариации тега.

Другие вопросы по тегам