LINQ эквивалент foreach для IEnumerable <T> - PullRequest
670 голосов
/ 14 октября 2008

Я бы хотел сделать в LINQ следующее, но я не могу понять, как:

IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());

Что такое настоящий синтаксис?

Ответы [ 21 ]

801 голосов
/ 14 октября 2008

Для IEnumerable нет расширения ForEach; только для List<T>. Так что вы могли бы сделать

items.ToList().ForEach(i => i.DoStuff());

В качестве альтернативы, напишите свой собственный метод расширения ForEach:

public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach(T item in enumeration)
    {
        action(item);
    }
}
345 голосов
/ 14 октября 2008

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

Это не значит, что это плохая вещь - просто думать о философских причинах решения.

36 голосов
/ 01 сентября 2011

Обновление 17.07.2012: по-видимому, начиная с C # 5.0, поведение foreach, описанное ниже, было изменено, и " использование итерационной переменной foreach во вложенном Лямбда-выражение больше не дает неожиданных результатов."Этот ответ не относится к C # ≥ 5.0.

@ Джон Скит и все, кто предпочитает ключевое слово foreach.

Проблема с «foreach» в C # до версии 5.0 заключается в том, что она несовместима с тем, как работает эквивалент «для понимания» на других языках, и с тем, как ожидайте, что это сработает (личное мнение изложено здесь только потому, что другие высказали свое мнение относительно читабельности). См. Все вопросы, касающиеся " Доступ к измененному закрытию " а также " Закрытие переменной цикла, считающейся вредной ". Это только «вредно» из-за способа, которым «foreach» реализован в C #.

Возьмите следующие примеры, используя функционально эквивалентный метод расширения, который приведен в ответе @Fredrik Kalseth.

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

Извинения за чрезмерно надуманный пример. Я использую только Observable, потому что делать что-то подобное не так уж и сложно. Очевидно, есть лучшие способы создать эту наблюдаемую, я только пытаюсь продемонстрировать точку. Обычно код, подписанный на наблюдаемое, выполняется асинхронно и, возможно, в другом потоке. Если использовать «foreach», это может привести к очень странным и потенциально недетерминированным результатам.

Проходит следующий тест с использованием метода расширения «ForEach»:

[Test]
public void ForEachExtensionWin()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                values.ForEach(value => 
                                    source.OnNext(() => value));

                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Win
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

С ошибкой происходит следующее:

Ожидается: эквивалентно <0, 1, 2, 3, 4, 5, 6, 7, 8, 9> Но было: <9, 9, 9, 9, 9, 9, 9, 9, 9, 9>

[Test]
public void ForEachKeywordFail()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                foreach (var value in values)
                                {
                                    //If you have resharper, notice the warning
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Fail
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}
34 голосов
/ 05 октября 2010

Вы можете использовать расширение FirstOrDefault(), которое доступно для IEnumerable<T>. Возвращая false из предиката, он будет запускаться для каждого элемента, но ему будет все равно, что он не найдет совпадения. Это позволит избежать накладных расходов ToList().

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
20 голосов
/ 02 июня 2010

Я взял метод Фредрика и изменил тип возвращаемого значения.

Таким образом, метод поддерживает отложенное выполнение , как и другие методы LINQ.

РЕДАКТИРОВАТЬ: Если это неясно, любое использование этого метода должно заканчиваться ToList () или любым другим способом, чтобы метод работал над полным перечислимым. В противном случае действие не будет выполнено!

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach (T item in enumeration)
    {
        action(item);
        yield return item;
    }
}

А вот тест, который поможет увидеть это:

[Test]
public void TestDefferedExecutionOfIEnumerableForEach()
{
    IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};

    var sb = new StringBuilder();

    enumerable
        .ForEach(c => sb.Append("1"))
        .ForEach(c => sb.Append("2"))
        .ToList();

    Assert.That(sb.ToString(), Is.EqualTo("121212"));
}

Если вы удалите ToList () в конце, вы увидите, что тест не пройден, так как StringBuilder содержит пустую строку. Это связано с тем, что ни один метод не заставлял ForEach перечислять.

18 голосов
/ 27 декабря 2012

Держите ваши побочные эффекты от моего IEnumerable

Я бы хотел сделать следующее в LINQ, но не могу понять, как:

Как уже отмечали другие здесь и за рубежом, методы LINQ и IEnumerable, как ожидается, не будут иметь побочных эффектов.

Вы действительно хотите что-то сделать с каждым элементом в IEnumerable? Тогда foreach - лучший выбор. Люди не удивляются, когда здесь происходят побочные эффекты.

foreach (var i in items) i.DoStuff();

Бьюсь об заклад, вы не хотите побочный эффект

Однако, по моему опыту, побочные эффекты обычно не требуются. Чаще всего возникает простой запрос LINQ, ожидающий обнаружения, сопровождаемый ответом StackOverflow.com, который Джон Скит, Эрик Липперт или Марк Гравелл объясняют, как делать то, что вы хотите!

Некоторые примеры

Если вы на самом деле просто агрегируете (накапливаете) какое-то значение, вам следует рассмотреть метод расширения Aggregate.

items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));

Возможно, вы хотите создать новый IEnumerable из существующих значений.

items.Select(x => Transform(x));

Или, может быть, вы хотите создать справочную таблицу:

items.ToLookup(x, x => GetTheKey(x))

Список (каламбур не совсем предназначен) возможностей можно продолжать и продолжать.

11 голосов
/ 31 июля 2015

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

public static class EnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
    {
        foreach (var item in enumeration)
        {
            action(item);
            yield return item;
        }
    }
}
10 голосов
/ 15 августа 2011

Существует экспериментальный выпуск Microsoft Interactive Extensions для LINQ (также на NuGet , см. профиль RxTeams для получения дополнительных ссылок). Видео на 9 канале хорошо это объясняет.

Документы предоставляются только в формате XML. Я запустил эту документацию в Sandcastle , чтобы она была в более читаемом формате. Разархивируйте архив документов и найдите index.html .

Среди многих других полезностей он обеспечивает ожидаемую реализацию ForEach. Это позволяет вам писать код так:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

numbers.ForEach(x => Console.WriteLine(x*x));
8 голосов
/ 25 октября 2015

Согласно PLINQ (доступно с .Net 4.0), вы можете сделать

IEnumerable<T>.AsParallel().ForAll() 

для выполнения параллельного цикла foreach в IEnumerable.

6 голосов
/ 31 августа 2010

Цель ForEach - вызывать побочные эффекты. IEnumerable для ленивого перечисления набора.

Эта концептуальная разница весьма заметна, если учесть ее.

SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));

Это не будет выполняться до тех пор, пока вы не сделаете "count" или "ToList ()" или что-то на нем. Это явно не то, что выражено.

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

...