LINQ эквивалент foreach для IEnumerable<T>
Я хотел бы сделать эквивалент следующего в LINQ, но я не могу понять, как:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
Какой реальный синтаксис?
22 ответа
Для ForEach нет расширения IEnumerable
; только для 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);
}
}
Фредрик предоставил исправление, но, возможно, стоит подумать, почему это не входит в рамки для начала. Я считаю, что идея заключается в том, чтобы операторы запросов LINQ не имели побочных эффектов и соответствовали разумно функциональному взгляду на мир. Очевидно, что ForEach с точностью до наоборот - конструкция, основанная исключительно на побочных эффектах.
Нельзя сказать, что это плохая вещь - просто думать о философских причинах решения.
Обновление от 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, 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()));
}
Вы могли бы использовать FirstOrDefault()
расширение, которое доступно для IEnumerable<T>
, Возвращая false
Исходя из предиката, он будет запускаться для каждого элемента, но ему будет все равно, что он не найдет совпадения. Это позволит избежать ToList()
накладные расходы.
IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
Так много ответов, но ВСЕ не могут определить одну очень серьезную проблему с настраиваемым универсальным ForEach
расширение: Производительность! А если точнее, использование памяти и сборщик мусора.
Рассмотрим пример ниже. Таргетинг.NET Framework 4.7.2
или .NET Core 3.1.401
, конфигурация Release
и платформа Any CPU
.
public static class Enumerables
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (T item in @this)
{
action(item);
}
}
}
class Program
{
private static void NoOp(int value) {}
static void Main(string[] args)
{
var list = Enumerable.Range(0, 10).ToList();
for (int i = 0; i < 1000000; i++)
{
// WithLinq(list);
// WithoutLinqNoGood(list);
WithoutLinq(list);
}
}
private static void WithoutLinq(List<int> list)
{
foreach (var item in list)
{
NoOp(item);
}
}
private static void WithLinq(IEnumerable<int> list) => list.ForEach(NoOp);
private static void WithoutLinqNoGood(IEnumerable<int> enumerable)
{
foreach (var item in enumerable)
{
NoOp(item);
}
}
}
На первый взгляд, все три варианта должны работать одинаково хорошо. Однако когдаForEach
метод extension вызывается много- много раз, в результате вы получите мусор, который подразумевает дорогостоящий сборщик мусора. Фактически, имея этоForEach
Было доказано, что метод extension на горячем пути полностью снижает производительность в нашем приложении с интенсивным циклом.
Точно так же еженедельно набираемые foreach
цикл также будет производить мусор, но он все равно будет быстрее и менее интенсивно использовать память, чем цикл ForEach
extension (который также страдает от распределения делегата).
Строго типизированный foreach: использование памяти
Еженедельно типизированный foreach: использование памяти
Расширение ForEach: использование памяти
Анализ
Для строго типизированного foreach
компилятор может использовать любой оптимизированный перечислитель (например, основанный на значении) класса, тогда как общий ForEach
extension должно возвращаться к универсальному перечислителю, который будет выделяться при каждом запуске. Кроме того, фактический делегат также подразумевает дополнительное распределение.
Вы получите такие же плохие результаты с WithoutLinqNoGood
метод. Здесь аргумент имеет типIEnumerable<int>
вместо того List<int>
подразумевает тот же тип распределения перечислителя.
Ниже приведены соответствующие различия в IL
. Перечислитель, основанный на значениях, безусловно, предпочтительнее!
IL_0001: callvirt instance class
[mscorlib]System.Collections.Generic.IEnumerator`1<!0>
class [mscorlib]System.Collections.Generic.IEnumerable`1<!!T>::GetEnumerator()
против
IL_0001: callvirt instance valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<!0>
class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
Вывод
ОП спросил, как позвонить ForEach()
на IEnumerable<T>
. Исходный ответ ясно показывает, как это можно сделать. Конечно, вы можете это сделать, но опять же; мой ответ ясно показывает, что не стоит.
Подтверждено такое же поведение при таргетинге .NET Core 3.1.401
(составление с Visual Studio 16.7.2
).
Держите ваши побочные эффекты от моего IEnumerable
Я хотел бы сделать эквивалент следующего в LINQ, но я не могу понять, как:
Как и другие отмечают здесь и за рубежом LINQ и IEnumerable
Ожидается, что методы не будут иметь побочных эффектов.
Вы действительно хотите "сделать что-то" с каждым элементом в IEnumerable? затем foreach
это лучший выбор. Люди не удивляются, когда здесь происходят побочные эффекты.
foreach (var i in items) i.DoStuff();
Бьюсь об заклад, вы не хотите побочный эффект
Однако по моему опыту побочные эффекты обычно не требуются. Чаще всего существует простой запрос LINQ, ожидающий своего открытия, сопровождаемый ответом Stackru.com от Jon Skeet, Eric Lippert или Marc Gravell, объясняющих, как делать то, что вы хотите!
Некоторые примеры
Если вы на самом деле просто агрегируете (накапливаете) какое-то значение, то вам следует рассмотреть Aggregate
метод расширения.
items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));
Возможно, вы хотите создать новый IEnumerable
из существующих значений.
items.Select(x => Transform(x));
Или, может быть, вы хотите создать справочную таблицу:
items.ToLookup(x, x => GetTheKey(x))
Список (каламбур не совсем предназначен) возможностей можно продолжать и продолжать.
Я взял метод Фредрика и изменил тип возвращаемого значения.
Таким образом, метод поддерживает отложенное выполнение, как и другие методы 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 перечислять.
Если вы хотите выступить в роли списков, вам нужно сдать каждый предмет.
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;
}
}
}
Существует экспериментальный выпуск 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));
Согласно PLINQ (доступно с версии.Net 4.0), вы можете сделать
IEnumerable<T>.AsParallel().ForAll()
сделать параллельный цикл foreach на IEnumerable.
Цель ForEach - вызывать побочные эффекты. IEnumerable предназначен для ленивого перечисления множества.
Это концептуальное различие весьма заметно, если учесть его.
SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));
Это не будет выполняться до тех пор, пока вы не сделаете "count" или "ToList()" или что-то на нем. Это явно не то, что выражается.
Вы должны использовать расширения IEnumerable для настройки цепочек итераций, определения контента по их соответствующим источникам и условиям. Деревья выражения являются мощными и эффективными, но вы должны научиться ценить их природу. И не только для программирования вокруг них, чтобы сохранить несколько символов, перекрывающих ленивую оценку.
Многие упоминали об этом, но я должен был это записать. Разве это не самый понятный / самый читаемый?
IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();
Коротко и просто (ст).
Как уже указывалось в многочисленных ответах, вы можете легко добавить такой метод расширения самостоятельно. Тем не менее, если вы не хотите этого делать, хотя я не знаю ничего подобного в BCL, в System
namespace, если у вас уже есть ссылка на Reactive Extension (а если нет, то вы должны иметь):
using System.Reactive.Linq;
items.ToObservable().Subscribe(i => i.DoStuff());
Хотя имена методов немного отличаются, конечный результат - именно то, что вы ищете.
Теперь у нас есть возможность...
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
parallelOptions.MaxDegreeOfParallelism = 1;
#endif
Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));
Конечно, это открывает новую банку с червями.
ps (извините за шрифты, это то, что система решила)
ForEach также можно приковать цепочкой, просто вернув его обратно в колоду после действия. оставаться в курсе
Employees.ForEach(e=>e.Act_A)
.ForEach(e=>e.Act_B)
.ForEach(e=>e.Act_C);
Orders //just for demo
.ForEach(o=> o.EmailBuyer() )
.ForEach(o=> o.ProcessBilling() )
.ForEach(o=> o.ProcessShipping());
//conditional
Employees
.ForEach(e=> { if(e.Salary<1000) e.Raise(0.10);})
.ForEach(e=> { if(e.Age >70 ) e.Retire();});
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enu, Action<T> action)
{
foreach (T item in enu) action(item);
return enu; // make action Chainable/Fluent
}
Код выше Edit2 работает, но лучше использует эту версию.
Редактировать ниже был неправильный пример, на который указал Тэмир. Большое спасибо.
Employees.ForEach(e=>e.Salary = e.Salary * 2)
.Where (e=> e.Salary > 10000)
.Average(e=> e.Salary);
Вдохновленный Джоном Скитом, я расширил его решение следующим:
Метод продления:
public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector)
{
foreach (var item in source)
{
var target = keySelector(item);
applyBehavior(target);
}
}
Клиент:
var jobs = new List<Job>()
{
new Job { Id = "XAML Developer" },
new Job { Id = "Assassin" },
new Job { Id = "Narco Trafficker" }
};
jobs.Execute(ApplyFilter, j => j.Id);
,,,
public void ApplyFilter(string filterId)
{
Debug.WriteLine(filterId);
}
Эта абстракция "функционального подхода" протекает очень долго. Ничто на уровне языка не предотвращает побочные эффекты. Пока вы можете заставить его вызывать лямбда / делегат для каждого элемента в контейнере - вы получите поведение "ForEach".
Вот, например, один из способов слияния srcDictionary в destDictionary (если ключ уже существует - перезаписывает)
это взлом, и его не следует использовать ни в одном рабочем коде.
var b = srcDictionary.Select(
x=>
{
destDictionary[x.Key] = x.Value;
return true;
}
).Count();
MoreLinq имеет IEnumerable<T>.ForEach
и множество других полезных расширений. Это, вероятно, не стоит зависимости только для ForEach
, но там много полезного.
Я с уважением не согласен с тем, что методы расширения ссылок не должны иметь побочных эффектов (не только потому, что это не так, любой делегат может выполнять побочные эффекты).
Учтите следующее:
public class Element {}
public Enum ProcessType
{
This = 0, That = 1, SomethingElse = 2
}
public class Class1
{
private Dictionary<ProcessType, Action<Element>> actions =
new Dictionary<ProcessType,Action<Element>>();
public Class1()
{
actions.Add( ProcessType.This, DoThis );
actions.Add( ProcessType.That, DoThat );
actions.Add( ProcessType.SomethingElse, DoSomethingElse );
}
// Element actions:
// This example defines 3 distict actions
// that can be applied to individual elements,
// But for the sake of the argument, make
// no assumption about how many distict
// actions there may, and that there could
// possibly be many more.
public void DoThis( Element element )
{
// Do something to element
}
public void DoThat( Element element )
{
// Do something to element
}
public void DoSomethingElse( Element element )
{
// Do something to element
}
public void Apply( ProcessType processType, IEnumerable<Element> elements )
{
Action<Element> action = null;
if( ! actions.TryGetValue( processType, out action ) )
throw new ArgumentException("processType");
foreach( element in elements )
action(element);
}
}
То, что показано в примере, на самом деле является лишь своего рода поздним связыванием, которое позволяет вызвать одно из многих возможных действий, имеющих побочные эффекты для последовательности элементов, без необходимости писать большую конструкцию переключателя для декодирования значения, которое определяет действие, и преобразования. это в соответствующий метод.
Чтобы оставаться в курсе, можно использовать такой трюк:
GetItems()
.Select(i => new Action(i.DoStuf)))
.Aggregate((a, b) => a + b)
.Invoke();
Для VB.NET вы должны использовать:
listVariable.ForEach(Sub(i) i.Property = "Value")
Еще один ForEach
пример
public static IList<AddressEntry> MapToDomain(IList<AddressModel> addresses)
{
var workingAddresses = new List<AddressEntry>();
addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a)));
return workingAddresses;
}
Если вы делаете это, например, потому что вам нужен индекс в вашей итерации, вы всегда можете использовать конструкцию Where:
linqObject.Where((obj, index) => {
DoWork(obj, index);
return true;
}).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute
Это дает дополнительное преимущество, заключающееся в том, что исходный массив возвращается "без изменений" (объекты, на которые ссылается список, одинаковы, хотя они могут не иметь одинаковых данных), что часто желательно в методологиях функционального / цепного программирования, таких как LINQ.