Какова роль IEnumerable <T>и почему я должен его использовать? - PullRequest
13 голосов
/ 06 июня 2011

Почему я должен использовать IEnumerable<T>, когда я могу обойтись ... скажем List<T>?В чем преимущество первого перед последним?

Ответы [ 10 ]

31 голосов
/ 06 июня 2011

IEnumerable<T> - это интерфейс, который сообщает нам, что мы можем перечислить последовательность T экземпляров. Если вам нужно разрешить кому-либо видеть и выполнять какие-либо действия для каждого объекта в коллекции, этого достаточно.

List<T>, с другой стороны, является конкретной реализацией IEnumerable<T>, которая хранит объекты определенным, известным способом. Внутренне, это может быть очень хорошим способом хранения ваших значений, которые вы выставляете через IEnumerable<T>, но List<T> не всегда подходит. Например, если вам не нужен доступ к элементам по индексу, но вы постоянно вставляете элементы в начале своей коллекции, а затем удаляете элементы из конца, Queue<T> было бы гораздо более подходящим для использования.

Используя IEnumerable<T> в своем API, вы можете гибко изменять внутреннюю реализацию в любое время без изменения любого другого кода . Это дает огромные преимущества с точки зрения обеспечения гибкости и удобства сопровождения вашего кода.

4 голосов
/ 13 апреля 2012

Об этом Джеффри-Рихтер пишет:

При объявлении типов параметров метода вы должны указать самый слабый из возможных типов, предпочитая интерфейсы базовым классам.Например, если вы пишете метод, который манипулирует коллекцией элементов, было бы лучше объявить параметр метода, используя интерфейс, такой как IEnumerable<T>, вместо того, чтобы использовать сильный тип данных, такой как List<T>, или даже более сильный.тип интерфейса, например ICollection<T> или IList<T>:

// Desired: This method uses a weak parameter type   
public void ManipulateItems<T>(IEnumerable<T> collection) { ... }  

// Undesired: This method uses a strong parameter type   
public void ManipulateItems<T>(List<T> collection) { ... }

Причина, конечно, в том, что кто-то может вызвать первый метод, передающий объект массива, объект List<T>, объект String объект и т. д. - любой объект, тип которого реализует IEnumerable<T>.Второй метод позволяет передавать только List<T> объектов;он не примет массив или объект String.Очевидно, что первый метод лучше, потому что он гораздо более гибок и может использоваться в гораздо более широком диапазоне сценариев.

Естественно, если вы пишете метод, который требует список (не просто какой-либо перечисляемый объект), тогда вы должны объявить тип параметра как IList<T>.Вы все равно должны избегать объявления типа параметра как List<T>.Использование IList<T> позволяет вызывающей стороне передавать массивы и любые другие объекты, тип которых реализует IList<T>.

С другой стороны, обычно лучше объявить возвращаемый тип метода , используясамый сильный возможный тип (пытаясь не привязывать себя к определенному типу).

2 голосов
/ 07 июня 2011

Используя концепцию итераторов, вы можете значительно улучшить качество алгоритма как с точки зрения скорости, так и с точки зрения использования памяти.

Давайте рассмотрим следующие два примера кода. Оба анализируют файл, один хранит строки в коллекции, другой использует перечислимые.

Первый пример: O (N) время и O (N) память:

IEnumerable<string> lines = SelectLines();
List<Item> items = lines.Select(l=>ParseToItem(l)).ToList();
var itemOfIterest = items.FirstOrDefault(IsItemOfIterest); 

Второй пример - O (N) время, O (1) память. Кроме того, даже если асимптотическая сложность по времени все еще равна O (N), он будет загружать в два раза меньше элементов, чем в первом примере, в среднем:

var itemOfIterest = lines.FirstOrDefault(l=>IsItemOfIterest(ParseToItem(l));

Вот код SelectLines ()

 IEnumerable<string> SelectLines()
 {
  ...
  using(var reader = ...)
  while((line=reader.ReadLine())!=null)
   yield return line;
 }

Вот почему в среднем загружается вдвое меньше элементов, чем в первом примере. Допустим, вероятность найти элемент в любой позиции в диапазоне файлов одинакова. В случае IEnumerable из файла будут считываться только строки, представляющие интересующий элемент. В случае вызова ToList для перечисляемого весь файл будет прочитан еще до начала поиска.

Конечно, список в первом примере будет содержать все элементы в памяти, поэтому использование памяти O (N).

2 голосов
/ 06 июня 2011

Различные реализации коллекций могут быть перечисляемыми;использование IEnumerable дает понять, что вас интересует перечислимость, а не структура базовой реализации коллекции.

Как уже упоминал г-н Копси, это дает преимущество в обеспечении разделения отреализации, но я согласен с тем, что четкое определение наименьшего подмножества функциональных возможностей интерфейса, насколько это возможно (т. е. использование IEnumerable вместо List, где это возможно), обеспечивает именно такое разделение, а также требует правильной философии проектирования.Иными словами, вы можете добиться развязки, но не достигнуть минимальной зависимости, но вы не можете добиться минимальной зависимости, не достигнув максимальной развязки.

1 голос
/ 06 июня 2011

Если вы планируете создать публичный API, лучше использовать IEnumerable, чем List<T>, потому что вы лучше используете самый минималистичный интерфейс / класс. List<T> позволяет получить доступ к объектам по индексу, если это необходимо.

Здесь - довольно хорошее руководство по использованию IEnumerable, ICollection, List<T> и т. Д.

1 голос
/ 06 июня 2011

Обычно вы не используете IEunumerable напрямую. Это базовый класс для ряда других коллекций, которые вы, скорее всего, будете использовать. Например, IEnumerable предоставляет возможность перебирать коллекцию с помощью foreach. Это используется многими наследующими классами, такими как List<T>. Но IEnumerable не предлагает метод сортировки (хотя вы можете использовать Linq для этого), в то время как некоторые другие универсальные коллекции, такие как List<T>, имеют такой метод.

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

0 голосов
/ 20 марта 2018

Зачем реализовывать IEnumerable в вашем классе?

Если вы пишете класс, а ваш класс реализует интерфейс IEnumerable (универсальный (T) или нет), который вы разрешаете любому потребителювашего класса, чтобы перебрать свою коллекцию, не зная, как она структурирована.

LinkedList реализован не так, как Очередь, Стек, BinaryTree, HashTable, Graph и т. Д. Коллекция, представленная вашим классом, может быть структурирована по-разному.

Как "Потребитель "(если вы пишете класс, а ваш класс потребляет / использует-использует объект класса, который реализует IEnumerable), вы можете использовать его, не заботясь о том, как он реализован.Иногда потребительский класс не заботится о реализации - он просто хочет просмотреть все элементы (распечатать их, изменить их, сравнить их? И т. Д.)

(То есть, как потребитель, если ваша задачаперебирать все элементы в классе BinaryTree, и вы пропустили этот урок в Data-Structures-101 - если кодировщик BinaryTree реализовал IEnumberable - вам повезло! Вам не нужно открывать книгу и изучать, как пройтидерево - но просто используйте оператор foreach для этого объекта, и все готово.)

Как «производитель» (пишущий класс, содержащий структуру данных / коллекцию), вы, возможно, не хотите, чтобы потребители вашегокласс, чтобы справиться с тем, как он структурирован (возможно, они могут его сломать).Таким образом, вы можете сделать коллекцию частной и предоставить доступ только к общедоступному IEnumerator.

Она также может обеспечить некоторую однородность - у коллекции может быть несколько способов перебора элементов (PreOrder, InOrder, PostOrder, Breadth First)., Depth First и т. Д.) - но есть только 1 реализация для IEnumerable.Вы можете использовать это, чтобы установить способ по умолчанию для перебора коллекции.

Зачем использовать IEnumerable в вашем методе?

Если я напишу метод, который принимает коллекцию, выполняет итерации по ней и выполняет действия с элементами (объединяет их? Сравниваетих? и т. д.) почему я должен ограничивать себя одним типом коллекций?

Написание этого метода public void Sum(List<int> list) {...} для суммирования всех элементов в коллекции означает, что я могу только получить список и суммировать его.Запись этого public void Sum(IEnumerable<int> collection) {...} означает, что я могу взять любой объект, который реализует IEnumberable (списки, очереди, стеки и т. Д.), А также суммировать все их элементы.

Другие вопросы

Есть также проблемы отложенного выполнения и неуправляемых ресурсов.IEnumerable использует синтаксис yield, который означает, что вы просматриваете каждый элемент отдельно и можете выполнять все виды вычислений до и после.И опять же, это происходит один за другим, поэтому вам не нужно хранить всю коллекцию при запуске.Вычисления фактически не будут выполняться, пока не начнется перечисление (то есть, пока вы не запустите цикл foreach).И это может быть полезным и более эффективным в определенных случаях.Например, ваш класс может не хранить какую-либо коллекцию в памяти, а выполнять итерацию по всем файлам, существующим в определенном каталоге, или элементам в определенной БД, или другим неуправляемым ресурсам.IEnumerable может вмешаться и сделать это за вас (вы также можете сделать это без IEnumerable, но IEnumberable «подходит» концептуально, плюс он дает вам возможность использовать объект, созданный в цикле foreach).

0 голосов
/ 13 ноября 2014
  1. IEnumerable использует преимущества отложенного выполнения, как описано здесь: IEnumerable vs List - что использовать? Как они работают?

  2. IEnumerable включает неявное преобразование ссылок для типов массивов, которые известны как ковариация. Рассмотрим следующий пример:

публичный абстрактный класс Vehicle { }

public class Car :Vehicle
{
}

private void doSomething1(IEnumerable<Vehicle> vehicles)
{

}

private void doSomething2(List<Vehicle> vehicles)
{

}

var vec = new List<Car>();
doSomething1(vec); // this is ok 
doSomething2(vec); // this will give a compilation error 
0 голосов
/ 09 июня 2011

Реализация IEnumerable обычно является предпочтительным способом для класса указывать, что он должен использоваться с циклом "foreach", и что несколько циклов "foreach" на одном и том же объекте должны работать независимо.Несмотря на то, что IEnumerable используется иначе, чем «foreach», обычное указание на то, что следует реализовывать IEnumerable, состоит в том, что в этом классе имеет смысл сказать «foreach foo in classItem {foo.do_something ();}.

0 голосов
/ 06 июня 2011

IEnumerable дает вам возможность реализовать собственную логику хранения и перебора коллекции объектов

...