Почему стандартный метод расширения на IEnumerables - PullRequest
1 голос
/ 16 ноября 2011

Когда я использую стандартный метод расширения в списке , такой как Где (...)

результат всегда IEnumerable , и когда вы решили сделать операцию со списком, такую ​​как Foreach ()

нам нужно привести (не очень) или использовать метод расширения ToList () , который

(возможно) использует новый список, который потребляет больше памяти (верно?):

List<string> myList=new List<string>(){//some data};

( Редактировать : этот актерский состав не работает)

myList.Where(p=>p.Length>5).Tolist().Foreach(...);

или

(myList.Where(p=>p.Length>5) as List<string>).Foreach(...);

Какой код лучше или есть третий способ?

Edit: Foreach - это образец, замените его на BinarySerach

myList.Where(p=>p.Length>5).Tolist().Binarysearch(...)

Ответы [ 4 ]

9 голосов
/ 16 ноября 2011

as определенно не очень хороший подход, и я был бы удивлен, если бы он работал.

С точки зрения того, что является "лучшим", я бы предложил foreach вместо ForEach:

foreach(var item in myList.Where(p=>p.Length>5)) {
    ... // do something with item
}

Если вы отчаянно хотите использовать методы списка, возможно:

myList.FindAll(p=>p.Length>5).ForEach(...);

или действительно

var result = myList.FindAll(p=>p.Length>5).BinarySearch(...);

, но учтите, чтодля этого (в отличие от первого) требуется дополнительная копия данных, что может быть затруднительно, если в myList 100 000 элементов длиной более 5.

Причина, по которой LINQвозвращает IEnumerable<T> в том смысле, что этот объект (LINQ-to-Objects) предназначен для компоновки и потоковой передачи, что не возможно при переходе к списку.Например, комбинация из нескольких where / select и т. Д. Должна , а не строго создавать много промежуточных списков (и, действительно, LINQ этого не делает).

Этоеще более важно, если учесть, что не все последовательности ограничены;есть бесконечные последовательности, например:

static IEnumerable<int> GetForever() {
    while(true) yield return 42;
}
var thisWorks = GetForever().Take(10).ToList();

как до ToList это составные итераторы, не генерация промежуточногосписок.Однако есть несколько буферизованных операций, таких как OrderBy, которые должны сначала прочитать все данные.Большинство операций LINQ являются потоковыми.

5 голосов
/ 16 ноября 2011

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

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

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

Итак, оглядываясь на ваш пример, лучший способ использовать результат оператора Where зависит от того, что вы хотите с ним делать!

// This assumes myList has 20,000 entries
// if .Where returned a new list we'd potentially double our memory!
var largeStrings = myList.Where(ss => ss.Length > 100);
foreach (var item in largeStrings)
{
    someContainer.Add(item);
}

// or if we supported an IEnumerable<T>
someContainer.AddRange(myList.Where(ss => ss.Length > 100));
3 голосов
/ 16 ноября 2011

Если вы хотите сделать простой foreach над списком, вы можете сделать так:

foreach (var item in myList.Where([Where clause]))
{
    // Do something with each item.
}
2 голосов
/ 16 ноября 2011

Вы не можете разыграть (as) IEnumerable<string> до List<string>.IEnumerable оценивает элементы при доступе к ним.Вызов ToList<string>() перечислит все элементы в коллекции и вернет новый список, что немного неэффективно, а также не нужно.Если вы хотите использовать метод расширения ForEach для любой коллекции, лучше написать новый метод расширения ForEach, который будет работать с любой коллекцией.

public static void ForEach<T>(this IEnumerable<T> enumerableList, Action<T> action)
{
    foreach(T item in enumerableList)
    {
        action(item);
    }
}
...