Ostrzeżenie to składa się z dwóch części. Pierwsza to ...
Dostęp do każdej zmiennej w zamknięciu
... co samo w sobie nie jest nieważne, ale na pierwszy rzut oka jest sprzeczne z intuicją. Bardzo trudno jest też zrobić to dobrze. (Do tego stopnia, że artykuł, do którego odsyłam poniżej, opisuje to jako „szkodliwe”).
Weź swoje zapytanie, zauważając, że kod, który wyciągnąłeś, jest w zasadzie rozszerzoną formą tego, co kompilator C # (przed C # 5) generuje dla foreach1 :
Nie rozumiem, dlaczego [poniższe stwierdzenie jest] nieprawidłowe:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...
Cóż, jest poprawna składniowo. A jeśli wszystko robisz w pętli przy użyciu wartości z sto wszystko jest dobre. Ale zamknięcie sdoprowadzi do zachowania sprzecznego z intuicją. Spójrz na następujący kod:
var countingActions = new List<Action>();
var numbers = from n in Enumerable.Range(1, 5)
select n.ToString(CultureInfo.InvariantCulture);
using (var enumerator = numbers.GetEnumerator())
{
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
Console.WriteLine("Creating an action where s == {0}", s);
Action action = () => Console.WriteLine("s == {0}", s);
countingActions.Add(action);
}
}
Jeśli uruchomisz ten kod, otrzymasz następujące dane wyjściowe konsoli:
Creating an action where s == 1
Creating an action where s == 2
Creating an action where s == 3
Creating an action where s == 4
Creating an action where s == 5
Tego oczekujesz.
Aby zobaczyć coś, czego prawdopodobnie się nie spodziewasz, uruchom następujący kod bezpośrednio po powyższym kodzie:
foreach (var action in countingActions)
action();
Otrzymasz następujące dane wyjściowe konsoli:
s == 5
s == 5
s == 5
s == 5
s == 5
Czemu? Ponieważ stworzyliśmy pięć funkcji, które wszystkie robią dokładnie to samo: wypisuje wartość s(którą zamknęliśmy). W rzeczywistości pełnią tę samą funkcję („Drukuj s”, „Drukuj s”, „Drukujs ” ...).
W momencie, w którym zamierzamy ich użyć, robią dokładnie to, o co prosimy: wypisują wartość s. Jeśli spojrzysz na ostatnią znaną wartość s, zobaczysz, że to jest 5. Więc s == 5pięć razy zostajemy wydrukowani na konsoli.
To jest dokładnie to, o co prosiliśmy, ale prawdopodobnie nie to, czego chcemy.
Druga część ostrzeżenia ...
Może mieć różne zachowanie po skompilowaniu z różnymi wersjami kompilatora.
... jest tym, czym jest. Począwszy od C # 5, kompilator generuje inny kod, który „zapobiega” temu zjawisku za pośrednictwemforeach .
Dlatego poniższy kod da różne wyniki w różnych wersjach kompilatora:
foreach (var n in numbers)
{
Action action = () => Console.WriteLine("n == {0}", n);
countingActions.Add(action);
}
W konsekwencji wygeneruje również ostrzeżenie R # :)
Mój pierwszy fragment kodu, powyżej, będzie wykazywał to samo zachowanie we wszystkich wersjach kompilatora, ponieważ nie używam foreach(raczej rozszerzyłem go tak, jak robią to kompilatory sprzed C # 5).
Czy to dotyczy wersji CLR?
Nie jestem pewien, o co tutaj pytasz.
Post Erica Lipperta mówi, że zmiana zachodzi „w C # 5”. Więcprawdopodobnie musisz kierować reklamy na .NET 4.5 lub nowszy z kompilatorem C # 5 lub nowszym, aby uzyskać nowe zachowanie, a wszystko, co było wcześniej, otrzymuje stare zachowanie.
Ale żeby było jasne, jest to funkcja kompilatora, a nie wersja .NET Framework.
Czy istnieje związek z IL?
Inny kod tworzy różne IL, więc w tym sensie istnieją konsekwencje dla wygenerowanego IL.
1 foreach jest znacznie bardziej powszechną konstrukcją niż kod, który opublikowałeś w komentarzu. Problem zwykle pojawia się w wyniku użycia foreach, a nie ręcznego wyliczania. Dlatego zmiany foreachw języku C # 5 pomagają zapobiec temu problemowi, ale nie całkowicie.