Mir unerklärliches Programmierphänomen beim Arbeiten mit Enumeratoren und Delegates

26/03/2010 - 15:58 von Tobias Burger | Report spam
Hallo,

bin letztens eher zufàllig auf ein Phànomen gestoßen, welches ich einfach
nicht erklàren kann, was hier passiert...
Habe für ein WPF-Projekt ein Enumerable mit RelayCommands
(ICommand-Implementierung mit Delegates) erstellt.
Da das Ganze etwas umfangreich wàre hier zu posten habe ich ein kleines
Programm geschrieben, welches das Verhalten nachbildet.

Jedenfalls der Succus des Ganzen ist, dass ich bei durchlaufen über foreach
das korrekte Ergebnis der drei Listenelemente bekomme, wenn ich es über den
Indexer aufrufe bekomme ich immer das Ergebnis des letzten Listenelementes,
wenn ich es hingegen so mache wie bei meinem dritten Ansatz klappt es
wiederum... Also soweit muss es was mit der "deferred execution" des
IEnumerable zu tun haben, aber wieso dies zutrifft kann ich mir nicht
erklàren... (hoffe das Beispiel ist nicht zu umfangreich)

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// richtiges Ergebnis
foreach (var @delegate in GetDelegates())
{
@delegate.Execute();
}

Console.WriteLine();

// falsches Ergebnis
var dlist = GetDelegates().ToList();
dlist[0].Execute();
dlist[1].Execute();
dlist[2].Execute();

Console.WriteLine();

// richtiges Ergebnis
var dlist2 = GetDelegates2().ToList();
dlist2[0].Execute();
dlist2[1].Execute();
dlist2[2].Execute();
}

private static IEnumerable<DelegatedClass> GetDelegates()
{
foreach (var wrapper in GetWrappers())
{
yield return new DelegatedClass(
() =>
{
Console.WriteLine("Ergebnis für wrapper: {0}",
wrapper.Value);
}
);
}
}

private static IEnumerable<DelegatedClass> GetDelegates2()
{
foreach (var wrapper in GetWrappers())
{
yield return GetDelegate2(wrapper);
}
}

private static DelegatedClass GetDelegate2(WrapperClass wrapper)
{
return new DelegatedClass(
() =>
{
Console.WriteLine("Ergebnis für wrapper: {0}",
wrapper.Value);
}
);
}

private static IEnumerable<WrapperClass> GetWrappers()
{
for (int i = 0; i < 3; i++)
{
yield return new WrapperClass()
{
Value = i
};
}
}
}

class WrapperClass
{
public int Value { get; set; }
}

class DelegatedClass
{
private Action _callback;
public int Value { get; set; }

public DelegatedClass(Action callback)
{
_callback = callback;
}

public void Execute()
{
_callback();
}
}
}
 

Lesen sie die antworten

#1 Martin Honnen
26/03/2010 - 18:25 | Warnen spam
Tobias Burger wrote:

private static IEnumerable<DelegatedClass> GetDelegates()
{
foreach (var wrapper in GetWrappers())
{
yield return new DelegatedClass(
() =>
{
Console.WriteLine("Ergebnis für wrapper: {0}",
wrapper.Value);
}
);
}
}



Google nach "closure": die anonyme Funktion, die dein lambda-Ausdruck
erzeugt, bindet die lokalen Variablen, in dem Fall die
"wrapper"-Variable. Wenn die anonyme Funktion spàter ausgeführt wird,
hat die Variable den Wert nach Ausführung von GetDelegates().
Um das zu verhindern, ist eine Möglickkeit das Setzen einer zusàtzlichen
Variablen innerhalb des foreach:

private static IEnumerable<DelegatedClass> GetDelegates()
{
foreach (var wrapper in GetWrappers())
{
var wrapper2 = wrapper;
yield return new DelegatedClass(
() =>
{
Console.WriteLine("Ergebnis für wrapper: {0}",
wrapper2.Value);
}
);
}
}



Martin Honnen MVP XML
http://msmvps.com/blogs/martin_honnen/

Ähnliche fragen