Was sind Closures?

Auf diese Frage habe ich in der Vergangenheit meistens keine Antwort bekommen. Mit diesem Blog Eintrag (und dem Talk auf der dotnet Cologne) versuche ich das zu ändern.

Closures sind in .NET ein relativ neues Phänomen. Sie entstanden mit Lambda Ausdrücken und funktionaler Programmierung. Wikipedia hat eine sehr schöne und knackige Definition.

Als Closure oder Funktionsabschluss bezeichnet man eine Programmfunktion, die sich ihren Erstellungskontext „merkt“. Beim Aufruf kann die Funktion dann auf diesen zugreifen, selbst wenn der Kontext außerhalb der Funktion schon nicht mehr existiert.

Das bedeutet also, das man irgendwie ein Konstrukt schafft, welches eine Funktion definiert, diese aber erst ausführt, wenn der Kontext der Funktion sich verändert hat

Verspätetes zählen

Ein einfaches Beispiel ist eine For-Schleife, die einen Funktionsaufruf definiert, der auf die Zahlvariable zugreift. Außerhalb der Schleife wird die Funktion ausgeführt. In dem Beispiel wird 10x eine Action definiert (Lambda), die den Wert des Zählers ausgibt. Wenn die Schleife beendet ist, werden die 10 Actions ausgeführt.

var actions = new List();

for (int i = 0; i < 10; i++)
    actions.Add(() => Console.WriteLine(i));

foreach (var action in actions)
    action.Invoke();
Die Frage ist nun, was passiert?
  • Möglichkeit 1: Es werden die Zahlen von 0-9 ausgegeben. Das haben wir ja auch Programmiert, oder?
  • Möglichkeit 2: Es wird eine Exception geworfen. Die Variable ‘i’ existiert nicht mehr, da der Gültigkeitsbereich verlassen wird.
  • Möglichkeit 3: Es wird immer 9 ausgegeben, da immer nur auf die Variable 1 zugegriffen wird.

Tatsächlich passiert nichts von dem ersten drei Möglichkeiten (aber am ehesten Möglichkeit 3). Es wird 10x die ‘10’ ausgegeben. Aber was ist tatsächlich passiert? Das .NET Framework hat (egal wie) festgestellt, dass die Variable ‘i’ in einer Funktion verwendet wird. Damit gehört ‘i’ zum Kontext der Funktion und wird (egal wie) gespeichert zusammen mit der Funktion für die spätere Verwendung. Das bedeutet, dass der Gültigkeitsbereich an die Funktion gebunden wird.

PS: Es wird übrigens 10x die ‘10’ (zehn)  ausgegeben, weil in der letzten Iteration die Variable auf 10 erhöht wird. Der Funktionsblock wird allerdings nicht ausgeführt, da er dann nicht mehr die Ausführbedingung erfüllt (i<10).

Die erwartete Exception (Möglichkeit 2) wird damit umgangen. Jetzt ist die Frage, wie man das richtige Verhalten erreichen kann.

Kleine Anweisung, großer Effekt

Wir haben also festgestellt, dass .NET (egal wie) den Kontext speichert, wenn eine Funktion eine Variable aus einem kleineren Gültigkeitsbereich benötigt. Die benutzte Variable wird also mit der (Lambda)-Funktion gespeichert. Damit liegt die Lösung auf der Hand: Für jede Iteration muss eine neue Variable (also Speicherbereich) definiert werden, die mit der Funktion gespeichert wird.

var actions = new List();

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

var j = i;

    actions.Add(() => Console.WriteLine(j));
}

foreach (var action in actions)
    action.Invoke();

Kein aber fein und nun wird das erwartete Ergebnis auf der Konsole ausgegeben, die Zahlen von 0-9. Allerdings wird dieses mal ‘i’ nicht gespeichert sondern wie erwartet nach der Schleife freigegeben.

Über Thomas Mentzel
C#, SharePoint, .NET

One Response to Was sind Closures?

  1. Hier ist noch ein interessanter Link zum Thema „Closures“: http://blog.uwe-grunwald.net/?p=107

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google Foto

Du kommentierst mit Deinem Google-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s

<span>%d</span> Bloggern gefällt das: