2010-07-01 13 views
5

Mam fragment kodu, że myślałem, będzie działać z powodu zamknięcia; jednak wynik jest inny. Co się tutaj dzieje, aby nie wytworzyć oczekiwanego wyniku (jednego z każdego słowa)?Odd (pętla/gwint/string/lambda) zachowanie w C#

Kod:

string[] source = new string[] {"this", "that", "other"}; 
List<Thread> testThreads = new List<Thread>(); 
foreach (string text in source) 
{ 
    testThreads.Add(new Thread(() => 
    { 
     Console.WriteLine(text); 
    })); 
} 

testThreads.ForEach(t => t.Start()) 

wyjściowa:

other 
other 
other 
+7

tu znowu .... – leppie

+1

To powinno być ostrzeżenie kompilatora jak to jest w VB. Myślę, że to głupie, że zespół C# nie ostrzega ludzi przed potencjalnymi problemami. –

+0

Duplikat: [C# - identyfikator foreach i zamknięć] (http://stackoverflow.com/questions/512166/c-the-foreach-identifier-and-closures) – Shog9

Odpowiedz

7

Ma to związek z faktem, że zamknięcia uchwycić samą zmienną bez oceniania go, aż rzeczywiście używany. Po zakończeniu pętli foreach wartość text jest "inna" i tak jak po zakończeniu pętli, w której wywoływana jest metoda, a w momencie wywołania wartość przechwyconej zmiennej text jest "inna", co stanowi wartość innej przechwyconej zmiennej text. See this blog post from Eric Lippert w celu uzyskania szczegółowych informacji. Wyjaśnia zachowanie i niektóre przyczyny.

+0

Bardziej szczegółowo, zamknięcie jest wykonywany tylko wtedy, gdy wywołana, gdy nie zdeklarowany. Do czasu, kiedy zostanie wywołany, dzieje się stwierdzenie, które robi Davy8. – spoulson

+0

Edytowane, aby miejmy nadzieję, być bardziej przejrzyste. – Davy8

2

Zamknięcia w C# nie uchwycić wartość tekstu w momencie stworzenia. Ponieważ pętla foreach kończy wykonanie przed którymś z wątków wykonania, ostatnia wartość text poświęca się każdy.

Można temu zaradzić:

string[] source = new string[] {"this", "that", "other"}; 
List<Thread> testThreads = new List<Thread>(); 

foreach (string text in source) 
{ 
    // Capture the text before using it in a closure 
    string capturedText = text; 

    testThreads.Add(new Thread(() => 
     { 
      Console.WriteLine(capturedText); 
     })); 
} 

testThreads.ForEach(t => t.Start()); 

Jak widać, to kod „wychwytuje” Wartość text wewnątrz każdej iteracji pętli for. Gwarantuje to, że zamknięcie otrzymuje unikatowe odniesienie dla każdej iteracji, zamiast dzielić to samo odniesienie na końcu.

0

Powodem tego jest to, że dzieje się przez chwilę zaczynają swoje wątki pętla została zakończona i wartość tekstu zmienna lokalna jest „inny”, więc po uruchomieniu wątki to co zostanie wydrukowany. To może być łatwo ustalony:

string[] source = new string[] {"this", "that", "other"}; 
foreach (string text in source) 
{ 
    new Thread(t => Console.WriteLine(t)).Start(text); 
} 
0

Inni wyjaśnił, dlaczego używasz do tego problemu.

szczęście poprawka jest bardzo proste:

foreach (string text in source) 
{ 
    string textLocal = text; // this is all you need to add 
    testThreads.Add(new Thread(() => 
    { 
     Console.WriteLine(textLocal); // well, and change this line 
    })); 
} 
4

Jest to klasyczny błąd przechwytywania zmiennej pętli. Ma to wpływ zarówno for i foreach pętle: zakładając typową konstrukcję, masz jedną zmienną na całej długości pętli. Gdy zmienna jest przechwytywane przez wyrażenie lambda lub metody anonimowej, to zmienna sama (nie wartość w momencie chwytania), który zostaje schwytany. Jeśli zmienisz wartość zmiennej, a następnie wykonasz delegata, delegat "zobaczy" tę zmianę.

Eric Lippert opisuje szczegółowo na swoim blogu: part 1, part 2.

Typowym rozwiązaniem jest wziąć kopię zmiennej wewnątrz pętli:

string[] source = new string[] {"this", "that", "other"}; 
List<Thread> testThreads = new List<Thread>(); 
foreach (string text in source) 
{ 
    string copy = text; 
    testThreads.Add(new Thread(() => 
    { 
     Console.WriteLine(copy); 
    })); 
} 

testThreads.ForEach(t => t.Start()) 

Powodem tego jest to, że działa każdy delegat będzie teraz uchwycić inny „Wystąpienie” zmiennej copy.Zmienna ujęte będzie jeden tworzony dla iteracji pętli - co jest przypisana wartość textdla tej iteracji. Oto i wszystko działa.

0

Zamknięcia/lambda nie może skutecznie wiązać się z foreach lub zmiennych licznika pętli. Skopiuj wartość do innej zmiennej lokalnej (nie zadeklarowanej jako zmienna foreach lub licznika) i będzie działać zgodnie z oczekiwaniami.