Korzystając z wyrażeń lambda lub anonimowych metod w języku C #, musimy uważać na dostęp do zmodyfikowanej pułapki zamknięcia . Na przykład:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Ze względu na zmodyfikowane zamknięcie powyższy kod spowoduje, że wszystkie Where
klauzule w zapytaniu będą oparte na końcowej wartości s
.
Jak wyjaśniono tutaj , dzieje się tak, ponieważ s
zmienna zadeklarowana w foreach
powyższej pętli jest tłumaczona w kompilatorze w następujący sposób:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
zamiast tego:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Jak wskazano tutaj , zadeklarowanie zmiennej poza pętlą nie ma żadnych korzyści w zakresie wydajności, aw normalnych okolicznościach jedynym powodem, dla którego mogę to zrobić, jest zaplanowanie użycia zmiennej poza zakresem pętli:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
Jednak zmiennych zdefiniowanych w foreach
pętli nie można używać poza pętlą:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Tak więc kompilator deklaruje zmienną w sposób, który czyni ją wysoce podatną na błąd, który często jest trudny do znalezienia i debugowania, a jednocześnie nie daje widocznych korzyści.
Czy istnieje coś, co można zrobić z foreach
pętlami w ten sposób, czego nie można było zrobić, gdyby były one skompilowane ze zmienną o zasięgu wewnętrznym, czy jest to tylko arbitralny wybór, który został dokonany, zanim anonimowe metody i wyrażenia lambda były dostępne lub powszechne, a które nie nie został zmieniony od tego czasu?
String s; foreach (s in strings) { ... }
?foreach
wyrażeniach lamda, które skutkują podobnym kodem, jak pokazano przez OP ...Odpowiedzi:
Twoja krytyka jest całkowicie uzasadniona.
Szczegółowo omawiam ten problem tutaj:
Zamykanie przez zmienną pętli uważane jest za szkodliwe
Ten ostatni. Specyfikacja C # 1.0 w rzeczywistości nie mówiła, czy zmienna pętli znajduje się wewnątrz czy poza ciałem pętli, ponieważ nie robiło to zauważalnej różnicy. Kiedy semantyka zamykania została wprowadzona w C # 2.0, wybrano opcję umieszczenia zmiennej pętli poza pętlą, zgodnie z pętlą „for”.
Myślę, że uczciwie jest powiedzieć, że wszyscy żałują tej decyzji. Jest to jeden z najgorszych „gotchas” w języku C # i zamierzamy wprowadzić przełomową zmianę, aby to naprawić. W C # 5 zmienna foreach loop będzie logicznie wewnątrz korpusu pętli, a zatem zamknięcia będą za każdym razem otrzymywały nową kopię.
for
Pętla nie zostanie zmieniony, a zmiana nie będzie „z powrotem przeniesiony” do poprzednich wersjach C #. Dlatego powinieneś nadal zachować ostrożność, używając tego idiomu.źródło
foreach
jest „bezpieczny”, alefor
nie jest.To, o co pytasz, zostało dokładnie omówione przez Erica Lipperta w jego blogu Zamykanie pętli przez zmienną uważaną za szkodliwą i jej następstwo.
Dla mnie najbardziej przekonującym argumentem jest to, że posiadanie nowej zmiennej w każdej iteracji byłoby niespójne z
for(;;)
pętlą stylu. Czy spodziewałbyś się nowegoint i
w każdej iteracjifor (int i = 0; i < 10; i++)
?Najczęstszym problemem związanym z tym zachowaniem jest zamknięcie zmiennej iteracyjnej i ma ona łatwe obejście:
Mój post na blogu na ten temat: Zamknięcie nad zmienną foreach w języku C # .
źródło
ref
.cut
na literatury / Vars icute
do utrzymywania ocenione wartości częściowo ocenianych zamknięcia).Ugryziony przez to mam zwyczaj włączać zmienne zdefiniowane lokalnie w najbardziej wewnętrzny zakres, którego używam do przeniesienia do dowolnego zamknięcia. W twoim przykładzie:
Ja robię:
Kiedy już będziesz miał ten nawyk, możesz go uniknąć w bardzo rzadkim przypadku, w którym zamierzałeś związać się z zewnętrznymi celami. Szczerze mówiąc, nie sądzę, żebym kiedykolwiek to zrobił.
źródło
W C # 5.0 problem został rozwiązany i można zamknąć zmienne pętli i uzyskać oczekiwane wyniki.
Specyfikacja języka mówi:
źródło