У перечислителей есть несколько очень хороших свойств, которые вы теряете при преобразовании их в список. А именно они:
- Использовать отложенное / ленивое выполнение
- Составимы
- Неограничен
Сначала я посмотрю на отложенное выполнение. Поп-тест: сколько раз следующий код будет повторять строки во входном файле?
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, вы берете точно такую же функцию и делаете ее более мощной, поскольку она может воздействовать на более разные типы данных.