2013-04-28 12 views
39

To działa prawidłowo (czyli zgodnie z oczekiwaniami) w C# 5.0:Schwytany Zamknięcie (Loop Variable) w C# 5.0

var actions = new List<Action>(); 
foreach (var i in Enumerable.Range(0, 10)) 
{ 
    actions.Add(() => Console.WriteLine(i)); 
} 
foreach (var act in actions) act(); 

Drukuje 0 do 9. Ale ten pokazuje 10 do 10 razy:

var actions = new List<Action>(); 
for (var i = 0; i < 10; i++) 
{ 
    actions.Add(() => Console.WriteLine(i)); 
} 
foreach (var act in actions) act(); 

Pytanie: To był problem, który mieliśmy w wersjach C# przed 5.0; więc musieliśmy użyć lokalnego elementu zastępczego dla pętli i jest on teraz naprawiony - w C# 5.0 - w pętlach "foreach". Ale nie w pętlach "dla"!

Jaki jest tego powód (nie naprawiający problemu również dla pętli for)?

+2

Masz na myśli "co to za rozumowanie, że nie jest ustalone również dla pętli' for' "? –

+0

Jestem zaskoczony, że działa nawet w pierwszym przypadku ... Od uruchomienia Akcji po wyjściu z zakresu foreach/for. 'Var i' nie powinien już istnieć. Dla mnie jest to bardzo "niebezpieczny" projekt. – LightStriker

+0

@ LightStriker: Nie; to jest funkcja. Nazywa się to zamknięciem. – SLaks

Odpowiedz

37

Jaki jest tego powód?

Zakładam, że masz na myśli "dlaczego nie zmieniono również pętli for?"

Odpowiedź jest taka, że ​​w przypadku pętli for istniejące zachowanie ma sens. Jeśli złamać for pętlę na:

  • inicjatora
  • stan
  • iterator
  • ciała

... wtedy pętla jest grubsza:

{ 
    initializer; 
    while (condition) 
    { 
     body; 
     iterator; 
    } 
} 

(Z wyjątkiem tego, że iterator jest wykonywane t koniec continue; oświadczenia, a także, oczywiście).

Część inicjalizacji logicznie zdarza się tylko raz, więc jest to zupełnie logiczne, że istnieje tylko jeden „zmiennej instancji”. Co więcej, nie ma naturalnej "początkowej" wartości zmiennej w każdej iteracji pętli - nie ma nic do powiedzenia, że ​​for ma postać deklarującą zmienną w inicjalizatorze, testującą ją w warunku i modyfikującą ją. w iteratorze. Czego można oczekiwać pętla jak to zrobić:

for (int i = 0, j = 10; i < j; i++) 
{ 
    if (someCondition) 
    { 
     j++; 
    } 
    actions.Add(() => Console.WriteLine(i, j)); 
} 

Porównaj to z foreach pętli który wygląda jakbyś deklarując oddzielną zmienną dla każdej iteracji. Heck, zmienna jest tylko do odczytu, co sprawia, że ​​jest ona nawet jedną zmienną, która zmienia się pomiędzy iteracjami. Sensownie jest myśleć o pętli foreach jako deklarującej nową zmienną tylko do odczytu w każdej iteracji z jej wartością pobraną z iteratora.

+5

(Trudno mi dyskutować z wysokiej rangi programistą z 559 kilogramami obok jego zdjęcia, ale :) Zdrowy rozsądek (część ograniczona do granic mojego umysłu) raczej się nie zgadza, aw wielu innych językach można zobaczyć, że działa zarówno jako, jak i na foreach. Przeróbmy pętlę while w ten sposób: var x = initializer(); while (condition) {feed_x_to_body (body); x = iterator(); } i spodziewam się, że funkcja feed_x_to_body (która jest zadaniem dla kompilatora) wykonuje to zadanie zgodnie z oczekiwaniami (a nie to, co jest logiczne zgodnie z podstawową implementacją - nie interesuje mnie kompilator!). Może się mylę; proszę więcej. –

+0

@KavehShahbazian: Nie, to nie działa pętla 'for'. Fakt, że wiele pętli 'for' * zdarza się *, aby zadeklarować dokładnie jedną zmienną w inicjalizatorze, jest nieistotny. Zmienna zadeklarowana w inicjatorze * jest * zmienną używaną w ciele pętli - nie oznacza to, że ciało otrzymuje zmienną * wartość *. W szczególności, ciało pętli może * modyfikować * także zmienną, która złamałaby twój model świata. –

+0

"Możliwość modyfikowania zmiennej" wyjaśnił mi.Jednak myślę, że kiedy tworzę nowy zakres (jak ciało lambda), wartość zmiennej powinna zostać odłączona od jej głównego zakresu. Ta część wciąż nie znalazła tego miejsca w moim umyśle i Byłoby miło z twojej strony, gdybyś poprowadził więcej o tej części. Ale zaznaczam twoją odpowiedź jako odpowiedź, ponieważ pasuje ona (obecny) do świata C#. Dzięki; –