Czy istnieje powód ponownego użycia zmiennej w foreach przez C#?
Używając wyrażeń lambda lub anonimowych metod w C#, musimy uważać na dostęp do zmodyfikowanego zamknięcia pułapki. 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 dotyczące zapytania będą oparte na ostatecznej wartości s
.
Jak wyjaśniono tutaj , dzieje się tak, ponieważ s
zmienna zadeklarowana w foreach
pętli powyżej jest przetłumaczona w ten sposób w kompilator:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
Zamiast tak:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Jak wspomniano tutaj, nie ma żadnych korzyści z deklarowania zmiennej poza pętlą, a w normalnych okolicznościach jedynym powodem, dla którego mogę myśleć o tym, jest to, że planujesz użyć zmiennej poza zakresem pętli:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
Zmienne zdefiniowane w pętli foreach
nie mogą być używane poza pętlą:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Więc kompilator deklaruje zmienną w sposób, który sprawia, że jest bardzo podatny na błędy, które często są trudne do znalezienia i debugowania, przy jednoczesnym braku dostrzegalnych korzyści.
Czy jest coś, co można zrobić z foreach
pętlami w ten sposób, czego nie można zrobić, gdyby były skompilowane ze zmienną o wewnętrznym zasięgu, czy jest to po prostu arbitralny wybór, który został dokonany zanim anonimowe metody i wyrażenia lambda były dostępne lub powszechne, i który nie został zmieniony od tego czasu?
4 answers
Twoja krytyka jest całkowicie uzasadniona.Kompilator deklaruje zmienną w sposób, który sprawia, że jest ona bardzo podatna na błędy, które często są trudne do znalezienia i debugowania, nie generując przy tym żadnych zauważalnych korzyści.
Omawiam ten problem szczegółowo tutaj:
Zamknięcie zmiennej pętli za szkodliwą
Ten ostatni. Specyfikacja C# 1.0 nie mówi, czy zmienna loop znajduje się wewnątrz, czy poza ciałem pętli, ponieważ nie ma zauważalnej różnicy. Kiedy semantyka closure została wprowadzona w C# 2.0, dokonano wyboru, aby umieścić zmienną loop poza pętlą, zgodnie z "for" pętla. Myślę, że można powiedzieć, że wszyscy żałują tej decyzji. Jest to jeden z najgorszych "gotchas" w C# i przyjmiemy przełomową zmianę, aby to naprawić. W C # 5 zmienna pętli foreach będzie logicznie wewnątrz ciała pętli, a więc closures otrzyma za każdym razem nową kopię.Czy jest coś, co można zrobić z pętlami foreach w ten sposób, czego nie można zrobić, jeśli zostały skompilowane z wewnętrznym zakresem zmienna? czy jest to po prostu arbitralny wybór, który został dokonany zanim anonimowe metody i wyrażenia lambda były dostępne lub powszechne, i który nie został zmieniony od tego czasu?
Pętla for
nie zostanie zmieniona, a zmiana nie zostanie "przeniesiona z powrotem" do poprzednich wersji C#. Dlatego należy zachować ostrożność, gdy używając tego idiomu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-11 16:20:55
To, o co prosisz, zostało dokładnie omówione przez Erica Lipperta w jego wpisie na blogu zamykającym zmienną pętli uważaną za szkodliwą i jej kontynuacją.
Dla mnie najbardziej przekonującym argumentem jest to, że posiadanie nowej zmiennej w każdej iteracji byłoby niezgodne z pętlą stylu for(;;)
. Czy spodziewasz się, że w każdej iteracji for (int i = 0; i < 10; i++)
pojawi się nowa int i
?
Najczęstszym problemem z tym zachowaniem jest zamknięcie nad zmienną iteracji i ma łatwy obejście:
foreach (var s in strings)
{
var s_for_closure = s;
query = query.Where(i => i.Prop == s_for_closure); // access to modified closure
Mój wpis na blogu o tym problemie: Zamknięcie nad zmienną foreach w C # .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-28 21:36:36
Ugryziony przez to, mam zwyczaj włączania lokalnie zdefiniowanych zmiennych w najbardziej wewnętrznym zakresie, którego używam do przeniesienia do dowolnego zamknięcia. W twoim przykładzie:
foreach (var s in strings)
query = query.Where(i => i.Prop == s); // access to modified closure
Robię:
foreach (var s in strings)
{
string search = s;
query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.
}
Kiedy już masz ten nawyk, możesz go uniknąć w bardzo rzadkim przypadku, w którym zamierzałeś związać się z zewnętrznymi lunetami. Szczerze mówiąc, nigdy tego nie robiłem.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-26 14:13:22
W C # 5.0, ten problem jest naprawiony i można zamknąć zmienne pętli i uzyskać oczekiwane wyniki.
Specyfikacja języka mówi:
8.8.4 twierdzenie foreach
(...)
Wypowiedź foreach postaci
foreach (V v in x) embedded-statement
Jest następnie rozszerzony do:
{ E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }
(...)
Umieszczenie
v
wewnątrz pętli while jest ważne dla tego, jak jest przechwytywane przez dowolną funkcję anonimową występującą w embedded-statement. Na przykład:int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) f = () => Console.WriteLine("First value: " + value); } f();
Jeśli
v
zostanie zadeklarowana poza pętlą while, będzie współdzielona wśród wszystkich iteracji, a jego wartość po pętli for byłaby wartość końcowa,13
, czyli to, co wyświetli wywołanief
. Zamiast tego, ponieważ każda iteracja ma swoją zmiennąv
, ta przechwycony przezf
w pierwszej iteracji będzie nadal trzymał wartość7
, czyli to, co zostanie wydrukowane. (Uwaga: wcześniejsze wersje C# zadeklarowanav
Poza pętlą while.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-03 13:58:51