Я хотел бы сделать эквивалент следующего в LINQ, но я не могу понять, как:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
Каков реальный синтаксис?
Я хотел бы сделать эквивалент следующего в LINQ, но я не могу понять, как:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
Каков реальный синтаксис?
Нет расширения 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);
}
}
Fredrik предоставил исправление, но, возможно, стоит подумать, почему это не в том, с чего начать. Я считаю, что идея заключается в том, что операторы запросов LINQ должны быть свободными от побочных эффектов, подходящими с разумно функциональным способом взглянуть на мир. Ясно, что ForEach - это прямо противоположное - чисто основанная на побочном эффекте конструкция.
Чтобы не сказать, что это плохо, нужно просто подумать о философских причинах решения.
Обновление от 17.07.2012: по-видимому, начиная с С# 5.0, поведение foreach
описанное ниже, было изменено, и " использование итерационной переменной foreach
во вложенном лямбда-выражении больше не приводит к непредвиденным результатам". Этот ответ не относится к С# ≥ 5.0.
@Джон Скит и все, кто предпочитает ключевое слово foreach.
Проблема с "foreach" в С# до 5.0 заключается в том, что он не согласуется с тем, как эквивалент "для понимания" работает на других языках, и с тем, как я ожидаю, что он будет работать (личное мнение изложено здесь только потому, что другие упоминали их мнение относительно читабельности). См. Все вопросы, касающиеся " Доступ к измененному закрытию ", а также " Закрытие по переменной цикла, считающейся вредной ". Это только "вредно" из-за способа, которым "foreach" реализован в С#.
Возьмите следующие примеры, используя функционально эквивалентный метод расширения, который приведен в ответе @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; });
Я взял метод Fredrik и изменил тип возврата.
Таким образом, метод поддерживает отложенное выполнение, как и другие методы LINQ.
EDIT: Если это неясно, любое использование этого метода должно завершиться с помощью 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 перечислить.
Я хотел бы сделать эквивалент следующего в 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))
Список (каламбур, не полностью предназначенный) возможностей продолжается и продолжается.
Если вы хотите выступить в роли подсчета, вы должны принести каждый элемент.
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));
Если вы можете использовать IQueryable <T> вместо IEnumerable <T> , тогда метод Select должен делать то, что вы хотите.
IQueryable<Item> items = GetItems();
IQueryable<Item> modifiedItems = items.Select(i => i.DoStuff());
Хотя, как указывает Мартин Харрис, Select() на самом деле не будет оцениваться до тех пор, пока вы не перечислите коллекцию, поэтому, если вы полагаетесь на DoStuff(), чтобы выполнить какой-то побочный эффект, вам лучше с чем-то как
var modifiedItems = items.Select(i => i.DoStuff()).ToList()
Метод выбора LINQ на самом деле не имеет ничего общего с ключевым словом SQL SELECT; то, что он делает, это применить функцию к каждому элементу в наборе и вернуть набор (lazy-rated!), содержащий результаты этих функций.
Согласно PLINQ (доступный с .Net 4.0), вы можете сделать
IEnumerable<T>.AsParallel().ForAll()
сделать параллельный цикл foreach в IEnumerable.
Цель ForEach - вызвать побочные эффекты. IEnumerable предназначен для ленивого перечисления набора.
Эта концептуальная разница совершенно очевидна, если вы ее рассматриваете.
SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));
Это не будет выполняться, пока вы не сделаете "счет" или "ToList()" или что-то на нем. Это явно не то, что выражено.
Вы должны использовать расширения IEnumerable для настройки цепочек итераций, определения содержимого по их соответствующим источникам и условиям. Выражение Деревья являются мощными и эффективными, но вы должны научиться ценить их природу. И не только для программирования вокруг них, чтобы сохранить несколько символов, отменяющих ленивую оценку.
Многие говорили об этом, но мне пришлось записать его. Разве это не самый ясный/наиболее читаемый?
IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();
Короткий и простой (st).
Как уже указывалось в многочисленных ответах, вы можете легко добавить такой метод расширения самостоятельно. Однако, если вы не хотите этого делать, хотя я не знаю ничего подобного в BCL, в пространстве имен System
есть опция, если у вас уже есть ссылка на Реактивное расширение (а если нет, то должны иметь):
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".
Здесь, например, один из способов слияния srcDictionary в destDictionary (если ключ уже существует - перезаписывается)
это взлом и не должен использоваться в любом производственном коде.
var b = srcDictionary.Select(
x=>
{
destDictionary[x.Key] = x.Value;
return true;
}
).Count();
Вдохновленный Джон Скит, я расширил его решение следующим образом:
Метод расширения:
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 также может быть прикован, просто положить обратно в pileline после действия. оставаться в курсе
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
}
Изменить: Ленивая версия использует возвращение доходности, как это.
public static IEnumerable<T> ForEachLazy<T>(this IEnumerable<T> enu, Action<T> action)
{
foreach (var item in enu)
{
action(item);
yield return item;
}
}
Ленивая версия должна быть материализована, например ToList(), иначе ничего не происходит. см. ниже отличные комментарии от ToolmakerSteve.
IQueryable<Product> query = Products.Where(...);
query.ForEachLazy(t => t.Price = t.Price + 1.00)
.ToList(); //without this line, below SubmitChanges() does nothing.
SubmitChanges();
Я храню и ForEach(), и ForEachLazy() в моей библиотеке.
Я не согласен с тем, что методы расширения ссылок должны быть свободны от побочных эффектов (не только потому, что они не являются, ни один делегат может выполнять побочные эффекты).
Рассмотрим следующее:
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);
}
}
То, что показывает этот пример, на самом деле является просто своего рода поздним связыванием, которое позволяет вызвать одно из многих возможных действий, имеющих побочные эффекты для последовательности элементов, без необходимости писать большую конструкцию переключателя для декодирования значения, которое определяет и перевести его в соответствующий метод.
Для VB.NET вы должны использовать:
listVariable.ForEach(Sub(i) i.Property = "Value")
MoreLinq имеет IEnumerable<T>.ForEach
и массу других полезных расширений. Вероятно, не стоит брать зависимость только для ForEach
, но там много полезного.
Чтобы оставаться в курсе, можно использовать такой трюк:
GetItems()
.Select(i => new Action(i.DoStuf)))
.Aggregate((a, b) => a + b)
.Invoke();
Еще один пример 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.
Мне интересно, почему никто не ответил, используя функцию делегата? В моем кодировании я нахожу более понятным и сжатым способом представления подпрограммы, которую вы хотите выполнить на итерации списка.
ПРИМЕЧАНИЕ. Из опыта я всегда предлагаю использовать ToList(), чтобы избежать проблем с индексацией, если ваш исходный список изменяется, см. пример ниже:
public class MyObject {
public string MyString;
public int MyInt;
}
List<MyObject> list = new List<MyObject> {
new MyObject { MyString = "Test1", MyInt = 1970 },
new MyObject { MyString = "Test2", MyInt = 2010 },
new MyObject { MyString = "Test3", MyInt = 2011 },
new MyObject { MyString = "Test4", MyInt = 1767 }
};
// simple filter, but notice the Remove() will work with ToList(), remove ToList()
// and the statement will fail on execution
list.ToList().ForEach(
delegate(MyObject item) {
if (item.MyInt > 1999)
list.Remove(item);
}
);