Обновление от 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()));
}
ForEach()
.