Hier ein kleines Beispiel:
public void DoSomething(int i)
{
var x = 2 * i;
}
private Action GetAction(int initial)
{
var i = 2 * initial;
return () => DoSomething(i);
}
public void CallAction()
{
Action a = GetAction(5);
a();
}
Die Variable i in GetAction ist lokal und wird im Lambda-Ausdruck () => DoSomething(i) festgehalten. Beim Aufruf der Action in CallAction ist i eigentlich schon gar nicht mehr im Scope. Wegen des Closures kann der Delegat trotzdem darauf zugreifen.
Soweit so gut. Jetzt ein kleines Beispiel wo mich dieses Feature ein kleine Debugging-Session gekostet hat. Dazu ein vereinfachtes Beispiel:
private List<Action> actions = new List<Action>();
private void CreateActions()
{
for (int i = 0; i < 10; i++)
{
var param = i * 2;
AddAction(() => DoSomething(param));
}
}
private void AddAction(Action a)
{
if (!actions.Contains(a))
actions.Add(a);
}
public void DoSomething(object i)
{
var x = 2 * (int)i;
}
Jetzt werden die Actions zunächst in einer Liste gespeichert, aber nur falls die gleiche Action nicht schon enthalten ist.
Die Preisfrage lautet: wie viele Einträge enthält die Liste actions nach der Ausführung von CreateActions?
Die richtige Antwort ist: 10. Denn obwohl der Ausdruck () => DoSomething(param) immer gleich aussieht, ist es durch den Closure jedesmal eine andere Action, weshalb die Bedingung !actions.Contains(a) niemals greift.
Bei folgender Varianten ist die Menge der Varianten in der
Liste gleich 1:
private void CreateActions2()
{
for (int i = 0; i < 10; i++)
{
AddAction(() => DoSomething(1));
}
}
Soweit klar, hier wird ja kein Element aus dem externen
Scope eingeschlossen.
Aber auch diese Variante liefert einen Eintrag in der Liste
actions:private object context = 1;
private void CreateActions3()
{
for (int i = 0; i < 10; i++)
{
context = 2 * i;
AddAction(() => DoSomething(context));
}
}
Hier ist die Variable context ein Referenztyp und das Closure schlíeßt nur die Addresse der Variablen mit ein.
Wenn man es mal so einfach hinschreibt, eigentlich logisch :-)