Почему 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
, Однако, если вы (программист) знаете, что есть только string
s там, вы можете делать кастинг во время выполнения:
IEnumerable<string> strings = objects.Cast<string>();
Кстати, вы можете посмотреть описание ковариации тега.