Aktualizacja 17.07.2012: Wygląda na to, że od C # 5.0 zachowanie foreach
opisane poniżej zostało zmienione i „ użycie foreach
zmiennej iteracyjnej w zagnieżdżonym wyrażeniu lambda nie daje już nieoczekiwanych wyników ” . Ta odpowiedź nie dotyczy C # ≥ 5.0 .
@John Skeet i wszyscy, którzy preferują słowo kluczowe foreach.
Problem z „foreach” w C # przed 5.0 polega na tym, że jest to niespójne z tym, jak działa odpowiednik „do zrozumienia” w innych językach, i z tym, jak bym się spodziewał, że zadziała (osobista opinia podana tutaj tylko dlatego, że inni wspominali o ich opinia na temat czytelności). Zobacz wszystkie pytania dotyczące „ Dostępu do zmodyfikowanego zamknięcia ”, a także „ Zamknięcia w pętli zmiennej uważanej za szkodliwą ”. Jest to „szkodliwe” tylko ze względu na sposób, w jaki „foreach” jest implementowany w języku C #.
Weź następujące przykłady, używając funkcjonalnie równoważnej metody rozszerzenia do tej z odpowiedzi @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);
}
}
}
Przepraszamy za zbyt wymyślony przykład. Używam Observable, ponieważ zrobienie czegoś takiego nie jest zbyt dalekie. Oczywiście są lepsze sposoby na stworzenie tego, co można zaobserwować, staram się tylko wykazać, o co chodzi. Zazwyczaj kod subskrybowany do obserwowalnego jest wykonywany asynchronicznie i potencjalnie w innym wątku. Jeśli użyjesz „foreach”, może to dać bardzo dziwne i potencjalnie niedeterministyczne wyniki.
Następujący test przy użyciu metody rozszerzenia „ForEach” przechodzi pomyślnie:
[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()));
}
Błąd kończy się następująco:
Oczekiwany: równoważny <0, 1, 2, 3, 4, 5, 6, 7, 8, 9> Ale był: <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()
.