Was sind Closures?

DE   C#

Auf diese Frage habe ich in der Vergangenheit meistens keine Antwort bekommen. Mit diesem Blog Eintrag 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. 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.

“Er tat was er konnte, um zu verbergen, dass er nicht konnte, was er tat.” -Gabriel Laub

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 festgestellt, dass die Variable ‘i’ in einer Funktion verwendet wird. Damit gehört ‘i’ zum Kontext der Funktion und wird 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. 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 den Kontext speichert, wenn eine Funktion eine Variable aus einem kleineren Gültigkeitsbereich benötigt. Die benutzte Variable wird also mit der (Lambda)-Funktion zusammen 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.

Klein 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.

( Dieser Artikel wurde von meinem alten Blog migriert )
Written on May 29, 2012