CodeGym /Kurse /C# SELF /Funktionen höherer Ordnung

Funktionen höherer Ordnung

C# SELF
Level 51, Lektion 2
Verfügbar

1. Einführung

Wahrscheinlich hat man dir in der Schule gesagt: "Das ist eine Funktion von einer Funktion". Funktionen höherer Ordnung (Higher-Order Functions, HOF) sind Funktionen, die entweder Funktionen als Argumente akzeptieren, oder Funktionen als Ergebnis zurückgeben, oder beides.

Einfach gesagt: Wenn deine Methode als Parameter eine andere Funktion (z.B. einen Delegate oder eine Lambda) bekommen kann, oder sie als Ergebnis zurückgibt — herzlichen Glückwunsch, du hast eine Funktion höherer Ordnung!

Eine Funktion höherer Ordnung ist wie ein Generalschlüssel: sie kann nicht nur eine Tür öffnen, sondern dir auch einen anderen Schlüssel geben, damit du später die richtige Tür aufschließt.

Anwendung im echten Leben

Das klingt alles interessant und nicht trivial. Aber wozu braucht ein C#-Entwickler im Alltag solche Verrenkungen? Die kurzgestellte Antwort:
Funktionen höherer Ordnung machen Code flexibel, wiederverwendbar und knapp.
Das ist die Grundlage für Dinge wie LINQ, das Filtern und Sortieren von Collections, das Bauen von Datenverarbeitungs-Pipelines, das Konfigurieren von Callbacks, Events und sogar Dependency Injection.

Ein paar reale Szenarien:

  • Das Verhalten einer Methode begrenzen, indem du die Logik hineinreichst (z.B. Filterung, Sortierung, Transformation).
  • Einen universellen Data-Handler schreiben, dem du die gewünschte Operation per Delegate "reinschraubst".
  • Verarbeitungsketten ("Pipelines") bauen, in denen jede Funktion die Daten nach ihrem Rezept modifiziert.
  • Plattformübergreifende Abstraktionen erstellen: Was in Windows, was in Linux? Einfach die passende Funktion übergeben.

2. Beispiele einfacher Funktionen höherer Ordnung

Funktion, die eine andere Funktion entgegennimmt

Der klassischste Fall — eine Methode, die einen Delegate oder eine Lambda entgegennimmt.


// Funktion höherer Ordnung: nimmt die Funktion process als Parameter
void ForEach<T>(IEnumerable<T> collection, Action<T> process)
{
    foreach (var item in collection)
    {
        process(item); // Ruf die übergebene Funktion auf
    }
}

// Verwendung:
var numbers = new List<int> { 1, 2, 3 };
ForEach(numbers, n => Console.WriteLine($"Element: {n}"));

Was passiert hier? Die Methode ForEach weiß nicht, was genau mit jedem Element passieren soll. Alles, was sie macht, ist den übergebenen Prozessor (process) aufzurufen. Dieser Prozessor kann alles sein — auf die Konsole schreiben, in die DB speichern, UI updaten usw.

Ja, so funktioniert die ForEach-Methode bei Collections in C#, und praktisch alle LINQ-Methoden sind Higher-Order-Functions!

Funktion, die eine Funktion zurückgibt

Und jetzt ein stärkeres Beispiel — eine Methode, die nicht nur nimmt, sondern auch eine Funktion zurückgibt.


// Funktion höherer Ordnung: gibt eine andere Funktion zurück
Func<int, int> CreateMultiplier(int factor)
{
    // Wir geben eine Lambda zurück, die die Variable factor verwendet
    return x => x * factor;
}

// Verwendung:
var multiplyBy10 = CreateMultiplier(10);
Console.WriteLine(multiplyBy10(7)); // 70

Hier gibt CreateMultiplier eine Funktion zurück, die ihr Argument mit einem vorgegebenen Faktor multipliziert. Das ist schon eher eine "Function Factory".

Funktion, die eine Funktion nimmt und eine zurückgibt


Func<int, int> Compose(Func<int, int> f, Func<int, int> g)
{
    // Gibt eine Funktion zurück, die zuerst g anwendet, dann f: f(g(x))
    return x => f(g(x));
}

// Verwendung:
Func<int, int> increment = x => x + 1;
Func<int, int> doubleIt = x => x * 2;

var incrementThenDouble = Compose(doubleIt, increment);

Console.WriteLine(incrementThenDouble(5)); // (5 + 1) * 2 = 12

Solche Kompositionen sind die Basis für Datenstream-Verarbeitung — z.B. wenn du ein Array mit Select, Where, OrderBy etc. verarbeitest.

3. Wie C# Funktionen höherer Ordnung unterstützt

In funktionalen Sprachen (Haskell, F#) sind Funktionen per Default Higher-Order. Aber auch C# (seit Version 2.0) unterstützt das Modell dank Delegates und Lambdas.

  • Delegates (Func, Action, Predicate) — Typen für Funktionen.
  • Lambda-Ausdrücke — Syntax, um Funktionen inline zu erstellen.
  • Methoden können Delegates annehmen und zurückgeben — also werden Higher-Order-Funktionen "out of the box" unterstützt.

Visuelle Darstellung

flowchart LR
    A[Daten] --> B[Funktion 1]
    B --> C[Funktion 2]
    C --> D[Ergebnis]
    subgraph "Pipeline der Verarbeitung (Higher-Order-Functions)"
        B
        C
    end

4. Wir entwickeln unsere App weiter

Wir bauen unser schrittweises Demo weiter — sagen wir, es wird ein "Mini-String-Handler".

Fügen wir eine einfache Higher-Order-Methode hinzu

Stell dir vor, wir haben eine Liste von Benutzernamen (Strings) und wollen sie beliebig mit Funktionen modifizieren.


// Methode, die eine Liste von Strings und eine Transformationsfunktion entgegennimmt
List<string> TransformNames(List<string> names, Func<string, string> transformer)
{
    var result = new List<string>();
    foreach (var name in names)
    {
        result.Add(transformer(name));
    }
    return result;
}

Wie nutzt man diese Methode?


var names = new List<string> { "Anna", "Boris", "Sergey" };

// In Großbuchstaben umwandeln
var upperNames = TransformNames(names, n => n.ToUpper());

// "Sehr geehrte(r)" zu jedem Namen hinzufügen
var politeNames = TransformNames(names, n => "Sehr geehrte(r) " + n);

foreach (var n in upperNames)
    Console.WriteLine(n); // ANNA, BORIS, SERGEY

foreach (var n in politeNames)
    Console.WriteLine(n); // Sehr geehrte(r) Anna, ...

Die Methode TransformNames ist universell: sie delegiert die Transformations-Logik an den übergebenen Parameter (transformer), z.B. an ToUpper oder irgendein anderes Rezept.

Anpassung an Typen

Unser Beispiel lässt sich leicht für beliebige Datentypen anpassen.


// Generische, universelle Methode - Higher-Order-Funktion, die mit jedem T arbeitet
List<TResult> Map<T, TResult>(List<T> items, Func<T, TResult> transformer)
{
    var result = new List<TResult>();
    foreach (var item in items)
    {
        result.Add(transformer(item));
    }
    return result;
}

Anwendung:


var numbers = new List<int> { 1, 2, 3 };
var doubled = Map(numbers, x => x * 2); // [2, 4, 6]
var strings = Map(numbers, x => $"Zahl: {x}"); // ["Zahl: 1", ...]

5. Filterung und Aggregation via Higher-Order-Funktionen

Filter- und Suchlogik wird lange schon über Funktionen höherer Ordnung realisiert.


// Filterung: Higher-Order-Funktion
List<T> Filter<T>(List<T> items, Predicate<T> criteria)
{
    var result = new List<T>();
    foreach (var item in items)
    {
        if (criteria(item)) // Ruf das Kriterien-Callback auf
        {
            result.Add(item);
        }
    }
    return result;
}

Wie verwenden:


var names = new List<string> { "Anna", "Boris", "Andrey" };
var aNames = Filter(names, n => n.StartsWith("A"));
// Ergebnis: "Anna", "Andrey"

6. Das Konzept der Funktionskomposition (function composition)

Funktionen höherer Ordnung ermöglichen nicht nur einzelne Funktionen zu nutzen, sondern sie zu Ketten zusammenzusetzen — sogenannte Kompositionen. In C# schreibt man das als Funktion, die zwei Funktionen nimmt und eine neue zurückgibt.


// Funktions-Kompositor: gibt eine Funktion zurück, die zuerst g, dann f anwendet
Func<T, TResult> Compose<T, TIntermediate, TResult>(
    Func<TIntermediate, TResult> f,
    Func<T, TIntermediate> g)
{
    return x => f(g(x));
}

// Beispiel:
Func<int, int> plusOne = n => n + 1;
Func<int, int> timesTwo = n => n * 2;

var plusOneThenDouble = Compose(timesTwo, plusOne);
Console.WriteLine(plusOneThenDouble(3)); // (3 + 1) * 2 = 8

7. Nützliche Nuancen

Vorgeschichte: warum es früher komplizierter war?

Vor Delegates und Lambdas haben Entwickler oft viele ähnliche Schleifen geschrieben und Code kopiert, um Daten zu "filtern", "transformieren" oder "gruppieren". Mit Funktionen höherer Ordnung kannst du den veränderlichen Teil des Verhaltens in separate Funktions-Parameter auslagern — das reduziert drastisch Duplikation und macht den Code ausdrucksstärker.

Ein bisschen syntaktischer Zucker: Funktionen als Ausdrücke

HOFs werden oft als expression-bodied Methoden geschrieben — knappe Einzeiler:


List<string> FilterNames(Predicate<string> pred) =>
    Names.Where(name => pred(name)).ToList();

List<TResult> MapNames<TResult>(Func<string, TResult> transformer) =>
    Names.Select(transformer).ToList();

Vergleich mit LINQ-Mechanismen

Schauen wir uns an, wie LINQ Funktionen höherer Ordnung nutzt:

LINQ-Methode Welchen Delegaten sie erwartet Zweck
Where
Func<T, bool>
Filtert Elemente
Select
Func<T, TResult>
Transformiert Elemente
OrderBy
Func<T, TKey>
Sortiert nach einem Key
Aggregate
Func<TAcc, T, TAcc>
Aggregiert (reduziert) die Collection
Any
Func<T, bool>
Prüft, ob ein Element die Bedingung erfüllt

All diese Methoden basieren auf der Idee von Funktionen höherer Ordnung: du schreibst die Regeln, die Standardbibliothek liefert die Infrastruktur.

8. Mögliche Fehler und Fallstricke

Verwirrung bei Delegate-Typen.
Am Anfang ist es nicht immer klar, wann Action, Func oder Predicate passend ist.
Tipp: Wenn die Funktion bool zurückgibt — meist Predicate. Wenn sie einen Wert zurückgibt — Func. Wenn sie nichts zurückgibt — Action.

Variable Capturing (closures).
Wenn eine zurückgegebene Funktion Variablen aus dem äußeren Scope nutzt, achte darauf, dass die Werte beim Aufruf noch stimmen. Variablen werden nicht kopiert, sie werden "captured".

Debugging von komplexen Ketten.
Wenn Funktionen zu langen Pipelines komponiert werden, ist es schwer zu sehen, welche Stufe die Daten falsch verarbeitet hat. Füge temporäre Logs und Kommentare hinzu:


n => {
  Console.WriteLine("Vor der Operation: " + n);
  var res = n * 2;
  Console.WriteLine("Nachher: " + res);
  return res;
}
2
Aufgabe
C# SELF, Level 51, Lektion 2
Gesperrt
Filtern mit Higher-Order-Funktionen
Filtern mit Higher-Order-Funktionen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION