2013-05-28 18 views
11

Poniższy kod wyprowadza 33 zamiast 012. Nie rozumiem dlaczego nowa zmienna loopScopedi nie jest przechwytywana w każdej iteracji, zamiast przechwytywania tej samej zmiennej.C# deklaracja zmiennych wewnątrz wyrażeń lambdy

Action[] actions = new Action[3]; 

for (int i = 0; i < 3; i++) 
{ 

    actions [i] =() => {int loopScopedi = i; Console.Write (loopScopedi);}; 
} 

foreach (Action a in actions) a();  // 333 

Hopwever, ten kod generuje 012. Jaka jest różnica między tymi dwoma?

Action[] actions = new Action[3]; 

for (int i = 0; i < 3; i++) 
{ 
    int loopScopedi = i; 
    actions [i] =() => Console.Write (loopScopedi); 
} 

foreach (Action a in actions) a();  // 012 
+1

Ah to jest zmodyfikowany problem zamknięcia - patrz http://stackoverflow.com/questions/235455/access-to-modified-closure –

+1

Problem zamknięcia: http://www.codethinked.com/c-closures-explained na przykład. Zachowanie jest inne w .net < 4.5 and > = 4.5 –

+2

Przeczytaj te 2 wpisy z Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2009/11/16/closing-over-to-loop-variable-part -dwa.aspx i http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx – Icarus

Odpowiedz

7

ten nazywany jest „dostęp do zmodyfikowanego zamknięcia”. Zasadniczo istnieje tylko jedna zmienna i i odnoszą się do niej wszystkie trzy lambdy. Na końcu zmienna i została zwiększona do 3, więc wszystkie trzy akcje drukują 3. (Zauważ, że int loopScopedi = i w lambda działa tylko raz zadzwonić lambda później).

W drugiej wersji, tworzysz nową int loopScopedi dla każdej iteracji, a ustawienie go do bieżącej wartości i, który jest 0 i 1 i 2 dla każdej iteracji.

Można spróbować wyobrazić sobie inline z lambdy widzieć wyraźniej, co się dzieje:

foreach (Action a in actions) 
{ 
    int loopScopedi = i; // i == 3, since this is after the for loop 
    Console.Write(loopScopedi); // always outputs 3 
} 

Versus:

foreach (Action a in actions) 
{ 
    // normally you could not refer to loopScopedi here, but the lambda lets you 
    // you have "captured" a reference to the loopScopedi variables in the lambda 
    // there are three loopScopedis that each saved a different value of i 
    // at the time that it was allocated 
    Console.Write(loopScopedi); // outputs 0, 1, 2 
} 
2

Jaka jest różnica między tymi dwoma?

Inny zakres.

W pierwszej pętli odwołujesz się do zmiennej i, która jest zdefiniowana w zasięgu pętli instrukcji for, podczas gdy w drugiej pętli używasz zmiennej lokalnej. Wynik 333 jest wynikiem tego, że twoja pierwsza pętla iteruje 3 razy i odpowiednio zmienna i jest zwiększana do 3, a następnie, gdy wywołasz akcje, wszystkie odnoszą się do zmiennej o tej samej (i).

W drugiej pętli używasz nową zmienną dla każdegoAction więc masz 012.

2

Zmienne złapanych w lambda są podniesiona do klasy dzielonego między lambda i kod zewnętrzny.

W swoim pierwszym przykładzie, i jest podnoszona raz i używana zarówno z for() i wszystkimi przekazanymi lambdami. Do czasu osiągnięcia numeru Console.WriteLine, i osiągnął 3 z pętli for().

W twoim drugim przykładzie, nowy loopScopedi jest podnoszony dla każdego biegu pętli, więc nie ma wpływu na kolejne pętle.

2

Chodzi o to, w jaki sposób C# obsługuje zamknięcia. W pierwszym przykładzie zamknięcie nie zostanie przechwycone poprawnie i zawsze będziesz używał ostatniej wartości; ale w drugim przykładzie przechwytujesz bieżącą wartość zmiennej pętli w symbolu zastępczym, a następnie używasz tego symbolu zastępczego; który zapewnia właściwe rozwiązanie.

I jest różnica między tym, jak C# przechwytuje zmienną pętli w pętlach foreach, a pętlach w C# 5.0 i poprzednich wersjach - zmianę zerwania.

Miałem (prawie) to samo pytanie i dowiedziałem się o tym here.