Die Funktion von Funktionen (Teil 2)
Kommentare

Da es spätestens seit C# 3.0 sehr einfach ist, lokal neue Funktionen zu erzeugen, liefert Microsoft in .NET 3.5 (auch für die Verwendung in LINQ) eine Reihe generischer Delegate-Typen mit, sodass Sie

Da es spätestens seit C# 3.0 sehr einfach ist, lokal neue Funktionen zu erzeugen, liefert Microsoft in .NET 3.5 (auch für die Verwendung in LINQ) eine Reihe generischer Delegate-Typen mit, sodass Sie nicht oft eigene Typen erzeugen müssen. Der Delegate-Typ Action entspricht OutputDelegate: Action myOutput = value => Console.WriteLine(value);. Action-Delegate-Typen sind immer solche, die keinen Rückgabetyp haben (der Rückgabetyp ist void). Für Funktionen, die Werte zurückliefern, stehen die Func-Delegate-Typen zur Verfügung: Func add = (x, y) => x + y; Lambda-Expressions bieten viele unterschiedliche Varianten für ihre Formatierung und die Details, die der Programmierer angeben kann.

Im letzten Beispiel werden die Typen der beiden Parameter x und y aus der Zuweisung an die Variable mit dem Delegatetype Func hergeleitet. Aus diesem Grund lässt C# nicht die Verwendung von „var“ zu, wenn anonyme Methoden oder Lambda-Expressions ablegt werden sollen – der Compiler könnte unter Umständen die Typen nicht herleiten. Die Typen von Parametern können, wenn notwendig, explizit angegeben werden: Func add = (int x, int y) => x + y; In Fällen, wo die einfache Rückgabe eines Werts nicht ausreichend ist, können vollständige Codeblöcke auf der rechten Seite des „goes to“-Operators verwendet werden. In diesem Fall ist es auch notwendig, „return“ explizit anzugeben.

Func add = (x, y) => {
  return x + y;
}  

Die Codeblöcke können in dieser Syntax (man spricht vom „Statement Body“ im Gegensatz zum „Expression Body“) beliebig lang sein. In der Praxis empfiehlt es sich aber nicht, zu viel Code in einer Lambda-Expression unterzubringen, denn der Sinn ist, einfache Funktionen zum Zweck besserer Modularisierung lokal erzeugen zu können. Solange Expression Bodies verwendet werden, sind Lambda-Expressions kompatibel mit den Expression Trees in .NET 3.5 – in Zukunft (in und nach .NET 4.0) sollen diese jedoch erweitert werden, um auch Statement Bodies zu unterstützen.

In aller Kürze geht es bei Expression Trees darum, den Code in (anonymen) Funktionen in einer abstrakten Form darzustellen, die vom Code aus analysiert und erzeugt werden kann. Darauf basieren z. B. die Funktionen von LINQ, die eine Abfrage aus C#-Code in SQL übersetzen. Ein letztes wichtiges Thema in diesem Zusammenhang sind Closures. DSie bieten eine sehr einfache und logische, aber gleichzeitig auch sehr verwirrende Funktionalität. Sehen Sie sich dieses Beispiel an:

static Func CreateAddFunction(int value) {
  return othervalue => value + othervalue;
}  

Die Funktion CreateAddFunction nimmt einen numerischen Parameter entgegen und erzeugt eine Funktion, die diesen Wert zu einem zweiten Parameter addiert. Diese dynamisch erzeugte Funktion wird zurückgegeben. Dieser Umgang mit anonymen Funktionen ist durchaus sinnvoll – so eröffnen sich daraus neue Ansätze, wie sie speziell in der funktionalen Programmierung verbreitet sind. In dem Beispiel gibt es ein Detail, das auf Anhieb etwas unverständlich ist: die Handhabung der Variable value. Diese ist zunächst ein Parameter, also eine lokale Variable, und solche Variablen werden normalerweise auf dem Stack abgelegt. Somit sollte diese Variable nach der Beendigung der Funktion CreateAddFunction nicht mehr existieren, da der Stack der Funktion nicht mehr verfügbar ist. Andererseits bietet C# aber die hier demonstrierten Möglichkeiten, Funktionen wie Datenelemente im Programm hin- und her zu übergeben und die dynamisch erzeugte Lambda-Expression verwendet die Variable value. Wenn value plötzlich nicht mehr verfügbar wäre, würde ein späterer Aufruf der anonymen Funktion aus dem Hauptprogramm zum Programmabsturz führen.

Tatsächlich passiert das nicht und der Grund dafür ist das Feature der Closures. Der Compiler erkennt die potentiell „gefährliche“ Situation und sorgt dafür, dass die Variable value über ihre normale Lebensdauer hinaus verfügbar gehalten wird. Technisch betrachtet funktioniert das, indem eine Klasse vom Compiler automatisch erzeugt und eine Instanz davon angelegt wird. Diese Klasse hat eine Property value und überall dort, wo scheinbar ein Zugriff auf die lokale Variable value stattfindet, wird tatsächlich auf diese Property der automatisch erzeugten Klasseninstanz zugegriffen. Somit bleibt die Variable solange im Zugriff wie eine Referenz auf die anonyme Funktion existiert. Umgangssprachlich wird meist dieses Datenelement, das den Wert speichert, der andernfalls verloren ginge, als „Closure“ bezeichnet. Das Thema der Funktionen und ihrer Verwendung ist umfangreich und zu all den besprochenen Themen gibt es viel mehr Details, als in diesem Artikel aufgegriffen werden können. Es sei empfohlen, sich mit diesen Themen weiter zu beschäftigen. Ein gutes Verständnis der Mechanismen ist der Produktivitätssteigerung sehr zuträglich und in einigen Fällen basieren neue Module im .NET-Framework so sehr auf ihnen, dass es Programmierer ohne dieses Verständnis deutlich schwerer haben.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -