Страницы

Поиск по вопросам

воскресенье, 9 февраля 2020 г.

В чем разница в массивах IEnumerable<T> и List<T>?

#c_sharp #linq #типы #ienumerable


Возможно вопрос не совсем корректный.
Допустим у нас имеется следующий код:

  List listValues = new List { 1, 2, 3 };
            var t1 = listValues.ToList();


В данном случае переменная t1 будет ссылаться на новый объект List с аналогичными
значениями.

IEnumerable listValues2 = new List { 1, 2, 3 };
        var t2 = listValues2.ToList();


Здесь, как я понял происходит то, что t2 ссылаться на новый объект IEnumerable с
аналогичными значениями (массив t2 содержит 1,2,3).

Разнится в том, что при добавлении элементов во втором случае надо явно привести
к типу List, так? 

 ((List)listValues2).Add(4);


И/или имеются еще различия в данном случае?
    


Ответы

Ответ 1



t2, как и t1, будет ссылаться на новый объект List. IEnumerable - интерфейс, создать экземпляр интерфейса нельзя. listValues2 - интерфейсная ссылка. Она может указывать на любой экземпляр любого класса, реализующего IEnumerable. Например, на экземпляр класса Queue или Stack. IEnumerable необходим для оператора foreach, так как определяет метод GetEnumerator(). В этом интерфейсе не определяется метод Add(). Этот метод определен в интерфейсе ICollection, который также реализуется классом List. Именно поэтому для добавления элементов по ссылке listValues2 приходится предварительно явно приводить типы. При этом, если ссылка listValues2 указывала на экземпляр класса, отличного от List произойдет исключение времени выполнения.

Ответ 2



Здесь, как я понял происходит то, что t2 ссылаться на новый объект IEnumerable с аналогичными значениями Тип времени выполнения в обоих случаях одинаковый - List. Более того, даже если заменить var ... на IEnumerable ..., ничего не изменится, тип времени выполнения останется тем же, так как метод ToList всегда возвращает объект типа List. (Кроме того, объект просто не может иметь тип IEnumerable, так как экземпляр интерфейса создать нельзя.) Чтобы убедиться на практике, выполните код: List listValues = new List { 1, 2, 3 }; var t1 = listValues.ToList(); Console.WriteLine(t1.GetType()); IEnumerable listValues2 = new List { 1, 2, 3 }; var t2 = listValues2.ToList(); Console.WriteLine(t2.GetType()); IEnumerable listValues3 = new List { 1, 2, 3 }; IEnumerable t3 = listValues3.ToList(); Console.WriteLine(t3.GetType()); Он выведет System.Collections.Generic.List`1[System.Int32] System.Collections.Generic.List`1[System.Int32] System.Collections.Generic.List`1[System.Int32] Чтобы все-таки получить тип времени выполнения, отличный от List, можно реализовать метод-итератор: static IEnumerable Foo(List list) { for (int i = 0; i < list.Count; i++) yield return list[i]; } Тогда var t4 = Foo(listValues); Console.WriteLine(t4.GetType()); Выведет имя автоматически сгенерированного компилятором типа-итератора (у меня ConsoleApplication1.Program+d__0)

Ответ 3



Давайте я попробую немножко объяснить по-своему. Зайду с другого конца: зачем на практике могут понадобиться интерфейсы? Допустим, вам понадобилось выводить на консоль содержимое списка List и вы написали такой метод для этого: static void Print(List list) { foreach (var n in list) Console.Write(n + " "); Console.WriteLine(); } Теперь можно его использовать: List list = new List { 1, 2, 3 }; Print(list); Удобно. Но что, если нам понадобилось вывести массив? Пробуем использовать этот же метод: int[] array = new int[] { 4, 5, 6 }; Print(array); Увы, это невозможно. Как быть? Давайте разберёмся, что именно делается со списком в этом методе: он перечисляется (enumerate). В него ничего не добавляется, не изменяется, не удаляется. Всё, что от него требуется - позволить себя перечислять (проходить по нему). Интерфейс IEnumerable как раз и означает, что тип, реализующий его, является перечислым - по нему можно пробежаться в цикле. Изменим наш метод следующим образом: static void Print(IEnumerable list) { foreach (var n in list) Console.Write(n + " "); Console.WriteLine(); } Теперь код Print(array); успешно компилируется и работает. Теперь мы можем передавать в этот метод любой тип, реализующий интерфейс IEnumerable. Конечно, здесь мы ограничены типом int, но оставим это за рамками обсуждения. Более того, другой программист, использующий нашу библиотеку с этим методом, по одной лишь сигнатуре этого метода скажет, что может произойти с коллекцией, которую он подаст на вход: она будет перечислена и только. А если метод принимает List, то внутри метода вполне может произойти добавление новых данных в список или их удаление, или другие нежелательные действия... Таким образом, интерфейсы позволяют использовать не один жёстко заданный тип, а множество разных. А также интерфейсы дают понять, какие действия разрешены. PS: ещё больше гарантий по неизменности данных дают интерфейсы IReadOnly*.

Ответ 4



Ответ немного припоздал, но всё-же. Важно понимать, что тип переменной и тип её значения - не одно и то же. К примеру: using System; using System.Collections.Generic; namespace CSrharpApplicationTest { internal class Program { private static void Main(string[] args) { IEnumerable x = new List(); Console.WriteLine(x.GetType()); } } } Тип переменной - IEnumerable, тип её значения - List. В обоих случаях в Ваших примерах тип t1 и t2 - List, благодаря вызову метода-расширения ToList(), реализация которого выглядит следующим образом: [__DynamicallyInvokable] public static List ToList(this IEnumerable source) { if (source == null) { throw Error.ArgumentNull("source"); } return new List(source); } , где конструктор List описывается так: [__DynamicallyInvokable] public List(IEnumerable collection) { if (collection == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); } ICollection collection2 = collection as ICollection; if (collection2 != null) { int count = collection2.Count; if (count == 0) { _items = _emptyArray; } else { _items = new T[count]; collection2.CopyTo(_items, 0); _size = count; } } else { _size = 0; _items = _emptyArray; foreach (T item in collection) { Add(item); } } } Разнится в том, что при добавлении элементов во втором случае надо явно привести к типу List, так? Да, во втором случае Вы указываете компилятору, что тип listValues2 - любой тип, реализующий IEnumerable. В интерфейсе IEnumerable, нет методов добавления/изменения/удаления элементов, ведь он описывает минимальный функционал для возможности перечисления по коллекции, соответственно компилятор не может дать гарантий, что то, что лежит внутри listValues2 - коллекция, с методом Add. Поэтому требуется явное приведение типа к List.

Комментариев нет:

Отправить комментарий