CodeGym /Kurse /C# SELF /Delegates als Typ für Lambda-Ausdrücke

Delegates als Typ für Lambda-Ausdrücke

C# SELF
Level 50 , Lektion 0
Verfügbar

1. Einführung

Stell dir vor, du hast eine Kaffeemaschine. Normalerweise brüht sie einfach Kaffee und mischt sich nicht ein, aber manchmal sagt der Chef: „Kannst du bitte nach dem Brühen laut 'Fertig!' rufen?“ — und dafür braucht man eine Konfiguration. Die Kaffeemaschine selbst schreiben wir nicht um. Wir erstellen einfach eine Funktion, die sie am Ende ausführt.

In C# übernehmen diese Rolle Delegates — sie erlauben, Code-Snippets (Methoden, Lambdas oder anonyme Methoden) an Methoden zu übergeben, damit die Methode sie im richtigen Moment aufruft. Kurz gesagt: ein Delegate ist ein Typ, der Referenzen auf Methoden mit einer bestimmten Signatur halten kann.

Definition eines Delegates

In C# definiert man einen Delegate mit dem Schlüsselwort delegate. Beispiel:

// Delegate, das auf Methoden verweist, die int akzeptieren und bool zurückgeben
public delegate bool PredicateInt(int x);

Jetzt kann eine Variable vom Typ PredicateInt auf jede Methode (oder Lambda!) zeigen, die einen int entgegennimmt und ein bool zurückgibt.

Wozu braucht man Delegates?

  • Logik als Argument übergeben (z.B. für Sortierung, Filterung, Event-Handling)
  • An Events abonnieren (dazu später mehr)
  • Callbacks implementieren
  • Flexible APIs, bei denen ein Teil des Verhaltens von der aufrufenden Seite definiert wird

Einfache visuelle Übersicht

Delegatetyp Signatur Beispielaufruf
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

2. Wie ein Lambda zum Delegate wird

Syntax

Wenn du einen Lambda-Ausdruck schreibst, z.B. x => x > 5, erstellst du im Grunde ein Delegate-Objekt. Ein Lambda „lebt“ nicht allein: es braucht immer einen Typ (jemand muss wissen, welche Parameter und welchen Rückgabewert es hat). Darum wird ein Lambda-Ausdruck in C# immer implizit (oder explizit) in einen Delegate konvertiert.

Beispiel 1: Delegate an eine Methode binden

// Wir definieren explizit einen Delegate
public delegate bool MyPredicate(int number);

class Program
{
    static void Main()
    {
        // Wir weisen einer Variable vom Typ MyPredicate ein Lambda zu
        MyPredicate isEven = x => x % 2 == 0;

        Console.WriteLine(isEven(4));  // true
        Console.WriteLine(isEven(7));  // false
    }
}

Beispiel 2: Verwendung der Standard-Delegates

C# enthält eine Sammlung generischer Standard-Delegates: Action, Func<>, Predicate<>. Die tauchen praktisch überall auf, wo du Lambdas schreibst.

// Wir verwenden Func
  
    Func
   
     isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false 
   
  

3. Standard-Delegates: Func, Action, Predicate

Func<...>

Wird verwendet für Methoden, die etwas entgegennehmen und etwas zurückgeben.

Signatur:
— Der letzte Typ ist der Rückgabewert, alle vorherigen sind die Parametertypen, z.B.:
Func<int, string> — nimmt ein int, gibt ein string zurück

Func
  
    intToString = number => "Zahl: " + number; Console.WriteLine(intToString(7)); // "Zahl: 7" 
  

Action<...>

Benutzt man, wenn etwas ausgeführt werden soll, aber nichts zurückgegeben wird (void).

Action
  
    printHello = name => Console.WriteLine("Hallo, " + name + "!"); printHello("Vasiliy"); // "Hallo, Vasiliy!" 
  

Predicate<T>

Im Grunde eine Abkürzung für Func<T, bool>. Wird verwendet, wenn man eine logische Überprüfung über ein Objekt braucht (true/false).

Predicate
  
    isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true 
  

Visuelle Spickzettel

Delegate Signatur Anwendung
Func<T1, TResult>
Result(T1)
Transformation, Projektion
Action<T1>
void(T1)
Nebenwirkungen, Ausgabe
Predicate<T>
bool(T)
Filterung, Suche

Welchen Delegatetyp wähle ich für ein Lambda?

  • Wenn ein Rückgabewert erwartet wird, nimm Func<...>
  • Wenn die Methode nichts zurückgibt (void), nutze Action<...>
  • Wenn du eine Bedingung prüfen willst, nimm Predicate<T>

Beispiel: Liste filtern

List
  
    numbers = new List
   
     { 1, 2, 3, 4, 5, 6 }; // Erwartet Predicate
    
      List
     
       evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6 
     
    
   
  

4. Nützliche Feinheiten

Lambda-Ausdrücke und Collection-Methoden: was passiert unter der Haube?

Wenn du eine Collection-Methode aufrufst und ein Lambda übergibst, z.B.:

var adults = users.Where(u => u.Age >= 18);

Die Methode Where erwartet ein Argument vom Typ Func<T, bool>. Deine Lambda u => u.Age >= 18 wird also vom Compiler in ein Delegate dieses Typs umgewandelt.

Blockdiagramm: wie das funktioniert


Dein Lambda           -->      C#-Compiler         -->     Delegate-Objekt (Func
  
   ) (u => u.Age >= 18) [Typ bekannt] (Bereit zum Aufruf in Where()) 
  

Typisierung im Detail: Typinferenz

Normalerweise wird der Delegatetyp vom Compiler automatisch inferiert, wenn der Kontext klar ist. Zum Beispiel erwartet die Methode List<T>.Find ein Predicate<T>, und der Compiler kennt den Parametertyp aus der Methodensignatur.

List
  
    words = new List
   
     { "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find erwartet Predicate
    
      Console.WriteLine(result); // "three" 
    
   
  

Ist der Kontext nicht klar, musst du dem Compiler helfen:

// Explizit den Typ angeben
Func
  
    check = x => x > 10; 
  

Delegates zurückgeben: Funktionsfabriken

Manche Methoden können Delegates zurückgeben — also „Fabriken von Funktionen“ erzeugen. Das ist praktisch, um dynamisches Verhalten zu generieren.

// Funktion, die einen Delegate (Lambda) zurückgibt
Func
  
    GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15 
  

Das funktioniert, weil das Lambda (x => x * factor) die Variable factor aus dem äußeren Kontext einfängt (closure) und als Objekt vom Typ Func<int, int> zurückgegeben wird.

5. Fehler und Missverständnisse mit Delegates und Lambdas

Signaturen stimmen nicht überein

Der Compiler lässt dich eine Lambda nicht in einen Delegate schreiben, wenn Parameter oder Rückgabetyp nicht passen.

Func
  
    f = x => "Du kannst keinen string zurückgeben!"; // Kompilierfehler 
  

Fehler, wenn man versucht, ein Lambda ohne Delegate zu benutzen

Du kannst nicht einfach ein Lambda schreiben und versuchen, es ohne Typ aufzurufen:

// Das funktioniert nicht - der Compiler kann den Typ nicht ableiten
// var myFunc = x => x * 2; // Fehler CS0815 
// myFunc(10);

Damit es funktioniert, musst du den Typ explizit angeben oder Kontext bereitstellen:

Func
  
    myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20 
  

Verwechslung zwischen Action, Func und Predicate

Manchmal wählt man versehentlich den falschen Delegatetyp, was zu Signaturfehlern führt. Merk dir die einfache Regel: Func — wenn es ein Ergebnis gibt, Action — wenn es kein Ergebnis gibt (void), Predicate<T> — wenn du eine boolesche Antwort brauchst (bool).

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION