Объяснение, почему IEnumerable более эффективен, чем список - PullRequest
45 голосов
/ 28 августа 2009

Я продолжаю слышать, что в .net 3.5 вы должны использовать IEnumerable вместо List, но я не могу найти никаких справочных материалов или статей, которые объясняют, почему он настолько опытный. Кто-нибудь знает какой-либо контент, который объясняет это?

Цель задать этот вопрос - лучше понять, что IEnumerable делает под капотом. Если вы можете предоставить мне какие-либо ссылки, я проведу исследование и отправлю ответ.

Ответы [ 7 ]

67 голосов
/ 28 августа 2009

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

Например, рассмотрим следующую сигнатуру метода:

void Output(List<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}

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

void Output(IEnumerable<Foo> foos) 
{ 
    foreach(var foo in foos) { /* do something */ }
}

Теперь мы используем самый общий (наименее специфический) интерфейс, который поддерживает необходимые нам операции. Это фундаментальный аспект ОО-дизайна. Мы уменьшили связь, требуя только то, что нам нужно, и не намного больше, кроме того. Мы также создали более гибкий метод , поскольку параметром foos может быть Queue<T>, List<T>, что угодно , которое реализует IEnumerable<T>. Мы не заставляем вызывающего пользователя без необходимости преобразовывать свою структуру данных в список.

Так что IEnumerable<T> не более эффективен, чем список в аспекте «производительность» или «время выполнения». Дело в том, что IEnumerable<T> является более эффективной конструкцией , потому что это более конкретное указание того, что требуется вашему дизайну. (Хотя это может привести к увеличению времени выполнения в определенных случаях.)

40 голосов
/ 28 августа 2009

У перечислителей есть несколько очень хороших свойств, которые вы теряете при преобразовании их в список. А именно они:

  • Использовать отложенное / ленивое выполнение
  • Составимы
  • Неограничен

Сначала я посмотрю на отложенное выполнение. Поп-тест: сколько раз следующий код будет повторять строки во входном файле?

IEnumerable<string> ReadLines(string fileName)
{
    using (var rdr = new StreamReader(fileName) )
    {
       string line;
       while ( (line = rdr.ReadLine()) != null) yield return line;
    }
}


var SearchIDs = new int[] {1234,4321, 9802};

var lines = ReadLines("SomeFile.txt")
              .Where(l => l.Length > 10 && l.StartsWith("ID: "));
              .Select(l => int.Parse(l.Substring(4).Trim()));
              .Intersect(SearchIDs);

Ответ точно один ноль. На самом деле он не выполняет никакой работы, пока вы не выполните итерации по результатам. Вам нужно добавить этот код, прежде чем он даже откроет файл:

foreach (string line in lines) Console.WriteLine(line);

Даже после запуска кода он по-прежнему зацикливается только на строках один раз. Сравните это с тем, сколько раз вам нужно перебрать строки в этом коде:

var SearchIDs = new int[] {1234,4321, 9802};
var lines = File.ReadAllLines("SomeFile.txt"); //creates a list
lines = lines.Where(l => l.Length > 10 && l.StartsWith("ID: ")).ToList();
var ids = lines.Select(l => int.Parse(l.Substring(4).Trim())).ToList();
ids = ids.Intersect(SearchIDs).ToList();

foreach (string line in lines) Console.WriteLine(line);

Даже если вы игнорируете вызов File.ReadAllLines() и используете тот же блок итератора из первого примера, первый пример все равно будет быстрее. Конечно, вы можете написать это так же быстро, используя списки, но для этого требуется связать код, который читает файл, с кодом, который его анализирует. И поэтому вы теряете еще одну важную особенность: составность .

Чтобы продемонстрировать компоновку, я добавлю одну заключительную функцию & mdash; неограниченный ряд. Рассмотрим следующее:

IEnumerable<int> Fibonacci()
{
   int n1 = 1, n2 = 0, n;
   yield return 1;
   while (true)
   {
        n = n1 + n2;
        yield return n;
        n2 = n1;
        n1 = n;
   }
}

Похоже, что это будет продолжаться вечно, но вы можете использовать свойства *1033* компонуемости *1033* IEnumerable для создания чего-то, что безопасно дает, скажем, первые 50 значений или каждое значение, которое меньше данного числа:

  foreach (int f in Fibonacci().Take(50)) { /* ... */ }
  foreach (int f in Fibonacci().TakeWhile(i => i < 1000000) { /* ... */ }

Наконец, IEnumerable просто более гибок. Если вам абсолютно не нужна возможность добавлять в список или получать доступ к элементам по индексу, вы почти всегда лучше пишете функции, чтобы принимать IEnumerables в качестве аргументов вместо списков. Зачем? Потому что вы все равно можете передать список функции, если хотите & mdash; Список является IEnumerable. В этом отношении, массив также, и много других типов коллекции хорошо. Поэтому, используя здесь IEnumerable, вы берете точно такую ​​же функцию и делаете ее более мощной, поскольку она может воздействовать на более разные типы данных.

5 голосов
/ 28 августа 2009

IEnumerable<T> не более эффективен, чем List<T>, поскольку List<T> равно IEnumerable<T>.

Интерфейс IEnumerable<T> - это просто способ .NET использовать шаблон итератора , не более того.

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

3 голосов
/ 28 августа 2009

Вопрос не в эффективности (хотя это может быть правдой), а в гибкости.

Ваш код становится более пригодным для повторного использования, если он может использовать IEnumerable вместо List. КАК ЭФФЕКТИВНО рассмотреть этот код: -

 function IEnumerable<int> GetDigits()
 {

    for(int i = 0; i < 10; i++)
       yield return i
 }

 function int Sum(List<int> numbers)
 {
    int result = 0; 
    foreach(int i in numbers)
      result += i;

    return i;
 }

Q : Как мне взять набор чисел, сгенерированных GetDigits, и получить сумму, чтобы сложить их?
A : мне нужно загрузить набор чисел из GetDigits в объект List и передать его в функцию Sum. При этом используется память, так как все цифры должны быть загружены в память, прежде чем их можно будет суммировать. Однако изменив подпись Sum на: -

 function int Sum(IEnumerable<int> numbers)

Значит, я могу сделать это: -

 int sumOfDigits = Sum(GetDigits());

Список не загружен в память. Мне нужно только хранилище для текущей цифры и суммируемой переменной в сумме.

1 голос
/ 28 августа 2009

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

1 голос
/ 28 августа 2009

Это два разных зверя, и вы не можете их реально сравнить. Например, в var q = from x in ... q - это IEnumerable, но под капотом он выполняет очень дорогой вызов базы данных.

IEnumerable - это просто интерфейс для шаблона проектирования Iterator, тогда как List / IList - это контейнер данных.

0 голосов
/ 28 августа 2009

В .NET 3.5 использование IEnumerable позволяет писать методы с отложенным выполнением, такие как:

<code>public class MyClass
{
   private <code>List<int></code> _listOne;
   private <code>List<int></code> _listTwo;<br/>
   public <code>IEnumerable<int></code>
   GetItems ()
   {
      foreach (int n in _listOne)
      {
         yield return n;
      }
      foreach (int n in _listTwo)
      {
         yield return n;
      }
   }
}

Это позволяет объединять два списка без создания нового List<int> объекта.

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