Mit jeder neuen Version des .NET Frameworks werden neue Bibliotheken sowie Sprachfeatures eingeführt. Neue Bibliotheken werden in der Regel von einer Vielzahl von Entwicklern angenommen und verwendet. Bei neuen Sprachfeatures sieht das meist anders aus. Oft sind diese zwar namentlich bekannt, werden aber nur vereinzelt verwendet. Der Einsatz neuer Sprachfeatures kann aber an vielen Stellen den Code lesbarer und effizienter gestalten. Die Kolumne „Im Fokus C#“ greift jeweils bestimmte Sprachmerkmale auf und stellt diese praktisch vor. Dabei werden nicht nur neue Merkmale erläutert, sondern auch Eigenschaften, die seit der ersten .NET-Version verfügbar sind. Die Kolumne startet mit Delegates und stellt die Nutzung anonymer Methoden vor.
Arbeitsweise von Delegates
Wörtlich übersetzt bedeutet Delegate „Sellvertreter“, wobei im deutschen Sprachgebrauch eher der Begriff „Funktionszeiger“ (Delegaten) üblich ist. Die beiden Begriffe beschreiben die Funktion eines Delegaten aber schon recht gut. Ein Delegate ist in der Lage, die Aufrufadresse einer Methode zu speichern. Somit „zeigt“ der Delegate auf die Funktion (Begriff: „Funktionszeiger“). Wird der Delegate aufgerufen, ruft dieser „stellvertretend“ die Methode auf, auf die der Delegate verweist (Begriff: „Stellvertreter“). Listing 1 zeigt die einfache Verwendung eines Delegates in C#.
delegate void SimpleDelegate(); public static void DelegateSimple() { SimpleDelegate myDel = new SimpleDelegate(CallMe); myDel(); } public static void CallMe() { Console.WriteLine("Delegate !"); }
Listing 1
Zunächst wird die Signatur der Methode für den Funktionszeiger durch die Deklaration des Delegaten festgelegt. Im Beispiel kann der definierte Delegate SimpleDelegate auf jede Methode verweisen, die keinen Rückgabewert sowie keine Parameter definiert. Die Bedingung erfüllt die Methode CallMe, die lediglich eine Ausgabe auf der Konsole ausgibt. Die Zuweisung dieser Methode zum Delegate erfolgt während der Neuanlage des Delegates über den Konstruktur. Wurde diese Verbindung hergestellt, kann über die angelegte Delegate-Variable die Methode aufgerufen werden. Auch die Verwendung von Parametern und Rückgabewerten ist möglich, hierbei ändert sich lediglich die Signaturdefinition des Delegaten. Ein entsprechendes Beispiel ist in Listing 2 zu sehen. Weiterführende Informationen zum Delegate-Typ sind unter [1] zu finden.
delegate int Add(int a, int b); public static void DelegateParameter() { Add myDel = new Add(Calculate); int sum = myDel(5, 10); } public static int Calculate(int x, int y) { return x + y; }
Listing 2
Verwendung von Delegates
Innerhalb des .NET Frameworks existieren zahlreiche Methoden, die ein Delegate als Parametertyp erwarten. Das Beispiel in Listing 3 verdeutlicht die Verwendung anhand der Methode FindAll der generischen List-Klasse.
static Listarr = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; public static void Delegates() { List result = arr.FindAll(new Predicate (IsMatch)); foreach (var item in result) Console.WriteLine(item); } public static bool IsMatch(int figure) { return (figure % 2)==0; }
Listing 3
Ziel der Implementierung ist es, die geraden Zahlen aus der int-Liste zu ermitteln. Die Methode FindAll erwartet dazu einen Funktionszeiger folgender Struktur: public delegate bool Predicate
Delegate Inference
Der C# Compiler besitzt seit der .NET-Version 2.0 noch eine weitere interessante Eigenschaft, die auch die Verwendung von Delegates vereinfacht. Dabei handelt es sich um das so genannte Delegate Inference. Diese Eigenschaft ermöglicht es dem Compiler, Rückschlüsse auf den benötigten Typ zu ziehen. Somit entfällt die explizite Instanziierung eines bestimmten Delegates. Listing 4 zeigt, wie sich Delegate Inference auf die Codierung auswirkt. Wie erkennbar ist, entfällt die explizite Erzeugung des Predicate-Objekts und es wird lediglich der Name der Methode angegeben.
static Listarr = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; public static void Test() { List result = arr.FindAll(IsMatch); foreach (var item in result) Console.WriteLine(item); } public static bool IsMatch(int figure) { return (figure % 2)==0; }
Listing 4
Während der Übersetzungszeit (Compile Time) ermittelt der Compiler als Erstes den nötigen Delegate-Typen. Danach prüft der Compiler, ob die angegebene Methode existiert und die Methodensignatur dem Delegate-Typen entspricht. Wenn beide Bedingungen zutreffen, erzeugt der Compiler intern das nötige Delegate-Objekt und weist diesem die angegebene Methode zu.