C# und Delegaten, Func, Action, Predicate

Was sind Delegates?

Beispiel:
public delegate string MeinDelegateName(string s);

Ein Delegate deklariert einen Datentyp (hier MeinDelegateName) anhand einer Funktionssignatur. Eine Funktionssignatur, welche den Aufbau einer Funktion darstellt, sieht folgendermaßen aus:

Rückgabewert DelegateName (Argumente)

Diesem Datentyp kann nun eine oder mehrere (siehe +=-Operator) Funktionen mit genau dieser Funktionssignatur zugewiesen werden, was das Aufrufen dieser Funktionen ermöglicht, ohne deren Namen zu kennen. Dadurch wird die Implementierung der Funktion von dem aufrufendem Code entkoppelt.

Ein Verwendungsbeispiel wären EventListener / Events, welchen über Delegates die Funktion übergeben wird, die beim Eintritt des Events aufgerufen werden soll. Über den += Operator können einem Event mehrere Funktionen zugewiesen werden (z.B. bei einem Windows-Fenster das OnLoad-Event).

Bei der Verwendung von Delegates muss also der Delegate-empfangende Code den Namen der Funktion (bzw. Methode) nicht wissen und kann diese trotzdem aufrufen. Delegates anonymisieren also Funktions- bzw. Methodennamen. Ein ähnlicher Mechanismus für Klassen stellen Interfaces zur Verfügung. Interfaces anonymisieren allerdings nicht per se Namen der deklarierten Methoden/Funktionen sondern lediglich der Klassennamen. Wäre eine Anonymisierung von Funktions- und Methodennamen gewünscht, könnten die Delegates in den Interfaces deklariert werden.

Deklaration von Delegate-Typen mit dem normalen Delegate-Schlüsselwort

// Definition des Delegatentyps SageHalloWeltDelegate:  
// Eine Funktionssignatur, welcher ein string übergeben
// wird und welche einen String zurückgibt, wird festgelegt
public delegate string SageHalloWeltDelegate(string s); 

public string sageHalloWeltFunktion(string s)
{
    Console.Write("Hallo");
    return " Welt";
}

public Program()
{
    // Instanz der Funktion "delegateInstanzDerFkt" mit der 
    // Funktionssignatur "SageHalloWeltDelegate"
    SageHalloWeltDelegate delegateInstanzDerFkt = sageHalloWeltFunktion;
    
    // Aufruf der instanzierten Funktion 
    Console.WriteLine(delegateInstanzDerFkt("Hallo"));		
}

Durch .NET vordefinierte Delegattypen: Action / Func / Predicate

 .NET enthält jedoch eine Reihe von Delegattypen, die Programmierer verwenden können, damit sie keine neuen Typen (wie oben der Typ „SageHalloWeltDelegate„) erstellen müssen. Diese Typen lauten Func<>Action<> und Predicate<>

Die Deklarationszeile des Delegate-Typs
public delegate string SageHalloWeltDelegate(string s);
fällt somit weg.

  • Action<> wird verwendet, wenn eine Aktion mithilfe der Argumente des Delegaten ausgeführt werden muss. Die von dem Typ gekapselte Methode gibt keinen Wert zurück.
    Die Signatur darf maximal 16 Argumente enthalten.

    Bsp. für eine Action
    Action<string> delegateInstanzDerFkt = sageHalloWeltFunktion;

    Hier würde der Teil “ Welt“ aus dem obigen Beispiel nicht zurückgegeben werden.
  • Func<> wird üblicherweise verwendet, wenn eine Transformation ausgeführt werden muss, Sie also die Argumente des Delegaten in ein anderes Ergebnis transformieren müssen. Projektionen sind ein gutes Beispiel. Die von dem Typ gekapselte Methode gibt einen angegebenen Wert zurück.
    Die Signatur darf maximal 16 Argumente enthalten.

    Bsp. für Rückgabewert „string“ und Übergabewert „string“:
    Func<string, string> delegateInstanzDerFkt = sageHalloWeltFunktion;

    Der letzte Typparameter der Func-Deklaration ist jeweils der Rückgabewert.
    Func<string, string, int> delegateInstanzDerFkt = sageHalloWeltFunktion;
    würde einen Integer-Wert zurückgeben und ihr würden zwei Strings übergeben werden.
  • Predicate<> wird verwendet, wenn Sie ermitteln müssen, ob das Argument die Bedingung des Delegaten erfüllt. Sie können auch die Schreibweise Func<T, bool> verwenden. In diesem Fall gibt die Methode einen booleschen Wert zurück.
    Predicates werden meistens bei LINQ verwendet, wenn man zum Beispiel in Collections oder Listen etwas suchen möchte. Meistens wird Predicate dann als LAMBDA-Ausdruck geschrieben.
    Die Signatur darf maximal 16 Argumente enthalten.

    Func<string, bool> delegateInstanzDerFkt = sageHalloWeltFunktion;
    ist also äquivalent mit
    Predicate<string> delegateInstanzDerFkt = sageHalloWeltFunktion;
    und würde mit dem obigen Beispiel zu einem Fehler führen, da die Methode sageHalloWeltFunktion einen String zurückgibt.

Konvertieren von Lamda-Ausrücken in Delegateinstanzen

// Berechne quadrat
Func<int, int> square = x => x * x; 
Console.WriteLine(square(5));

// Konkateniere String
Func<string, string, string> concat = (a,b) => a + " " + b;
concat("Hello", "World");

// Einzelne Action wird ohne Übergabeparameter (Rückgabe hat sie nicht)
Action line = () => Console.WriteLine("Hello world!");
line();

// Einzelne Action wird ohne Übergabeparameter (Rückgabe hat sie nicht)
Action line = text => Console.WriteLine(text);
line("Hello world!");

Example: