Bis vor gut einem Jahr war ich noch recht skeptisch, was die universelle Anwendung funktionaler Sprachelemente in C# anging. Die gab es von Anfang an, die Delegaten (delegates). Delegaten sind im Prinzip nichts anderes als typsichere Zeiger auf Funktionen. In nicht-typsicherer Form gibt es sie auch in C (und vermutlich auch in manchen Pascal-Dialekten). In C# Version 1 (C#1) bildeten Delegaten die saubere und transparente Grundlage für Ereignisse (Events), ein wesentliches Konstruktionselement grafischer Benutzeroberflächen, aber nicht ganz der reinen OO-Lehre entsprechend.
(Java ging einen anderen Weg und verpackte jedes Ereignis in eine Schnittstelle (Interface) und deren Implementierung in eine Klasse. Diese Klassen sind für Ereignisverarbeitung in der Regel anonym, und mit der Zulässigkeit solcher anonymen Klassen und vor allen deren Nebenwirkungen weicht auch Java von der reinen OO-Lehre ab.)
Die C#-Delegaten enthalten eine - möglicherweise auch bei MS zunächst gar nicht entdeckte - ungeahnte Mächtigkeit, die nur eben nicht aus dem OO-Paradigma, sondern aus der funktionalen Programmierung stammt.
Schon in C#1 sind Delegaten auch jenseits von Ereignissen oft einfacher und schneller anzuwenden, als jeweils Schnittstellen und diese implementierende Klassen zu schreiben.
Ab C#2 und vor allem mit C#3 aber tun sich neue Wege auf, die bei schließlich verbesserter Lesbarkeit weniger Schreibarbeit erfordern. Ich will das an einem Beispiel illustrieren. Das Beispiel ist nur endlich elegant, denn es ist eingebettet in das zwar weiterhin sehr praktische Windows-Forms, das aber ein wenig darunter leidet, ohne die Typsicherheit der Generika auskommen zu müssen.
Das Beispiel soll auf Knopfdruck eine Gruppe Steuerelementen aktivieren oder deaktivieren, in diesem Fall seien es Radio-Buttons.
Zunächst C#1. Die Radio-Buttons sollen sich in einer extra Liste (ArrayList) befinden. Natürlich könnte man auch Form.Controls nutzen, aber diese Collection bietet später für C#2 und C#3 nicht die erweitere Funktionalität. Der Einheitlichkeit wegen also diese Wahl.
Code: Alles auswählen
// C# 1
private void button1_Click(object sender, EventArgs e) {
foreach (Object o in radioButtons1) {
RadioButton rb = o as RadioButton;
if (rb != null)
rb.Enabled = checkBoxEnable.Checked;
}
}
Mit C#2 kamen die Generika und damit Typsicherheit für Collections. Große Erleichterung. Im anschließenden ersten C#2-Beispiel aber mache ich unsere Aktivierung/Deaktivierung der Radio-Button zunächst deutliche komplexer als zuvor. Es soll hierbei das Prinzip aufgezeigt werden, dass im folgenden weiter verwendet wird.
C#2 bietet Collections, die als Methode direkt ForEach anbieten. Die Schleife läuft darin intern, ich muss sie nicht mehr ausformulieren. Meine Radio-Buttons stehen jetzt in einer List<RadioButton>. Ich will ForEach anwenden. ForEach enthält einen Parameter, nämlich das, was in der Schleife getan werden soll. Dieser Parameter ist ein Delegat, der auf eine Funktion verweist, die ausgeführt werden soll. In diesem Fall ist der Delegat vordefiniert:
Code: Alles auswählen
delegate void Action<T> (T t);
Code: Alles auswählen
void action(RadioButton rb) {
rb.Enabled = checkBoxEnable.Checked;
}
// C# 1-2 ForEach with explictit delegate
private void button12_Click(object sender, EventArgs e) {
Action<RadioButton> actionDelegate = new Action<RadioButton>(action);
radioButtons2.ForEach(actionDelegate);
}
C#2 hat aber noch andere Spracherweiterungen mit sich gebracht, z.B. die anonymen Methoden. Anonyme Methoden ermöglichen eine vereinfachte Schreibweise für Delegaten, bei denen Methoden-Definition und Instantiierung zusammengefasst werden (für einmalige Anwendung). Das sieht dann so aus:
Code: Alles auswählen
// C# 2 ForEach with anonymous method
private void button2_Click(object sender, EventArgs e) {
radioButtons2.ForEach(delegate(RadioButton rb) {
rb.Enabled = checkBoxEnable.Checked;
});
}
Bringen wir nun C#3 ins Spiel und die Lambda-Ausdrücke. Lambda-Ausdrücke sind eine Darstellungsform für Funktionen, und stammen aus der bereits oben erwähnten Funktionalen Programmierung, ein für Normalanwender immer noch fremdes Paradigma und nicht zu verwechseln mit prozeduraler Programmierung aus der Zeit vor OO. In C# sind Lambda-Ausdrücke eigentlich nur eine neue Darstellungsform für anonyme Methoden. Aber endlich sparen wir tatsächlich Code und gewinnen gleichzeitig Lesbarkeit.
Code: Alles auswählen
// C# 3 ForEach with lambda expression
private void button3_Click(object sender, EventArgs e) {
radioButtons2.ForEach(rb => rb.Enabled = checkBoxEnable.Checked);
}
Vereinfachte Syntax, bessere Lesbarkeit, aber kein neues Konzept, sondern nur eine neue Darstellung für Delegaten mit anonymen Methoden: Das verbirgt sich hinter Lambda-Ausdrücken. Und wenn man den Zusammenhang mit den Delegaten verstanden hat, schwindet auch die Skepsis.