Funktionale Programmierung in C#: Von der Theorie zur Praxis
Funktionale Programmierung (FP) ist ein Paradigma, bei dem Berechnungen als Auswertung mathematischer Funktionen betrachtet werden, wobei Zustandsänderungen und veränderliche Daten vermieden werden. Obwohl C# ursprünglich eine objektorientierte Sprache war, hat es ab Version 3.0 aktiv funktionale Möglichkeiten aufgenommen. Heute unterstützt C# Konzepte wie Lambda-Ausdrücke, LINQ, unveränderliche Typen und sogar Pattern Matching. In diesem Artikel werden wir die wichtigsten Prinzipien der FP erläutern und zeigen, wie man sie in realen C#-Projekten anwendet.
1. Reine Funktionen und das Fehlen von Seiteneffekten
Eine reine Funktion ist eine Funktion, die:
- Für dieselben Argumente immer dasselbe Ergebnis zurückgibt (referenzielle Transparenz).
- Keine Seiteneffekte hat (keinen globalen Zustand ändert, nicht in Dateien schreibt, keine HTTP-Anfragen sendet).
In C# kann eine reine Funktion so aussehen:
public static int Add(int a, int b) => a + b;Und hier ein Beispiel für eine unreine Funktion:
public static int AddAndLog(int a, int b){ Console.WriteLine($"Addiere {a} und {b}"); // Seiteneffekt return a + b;}Vorteile reiner Funktionen: Sie sind leicht zu testen, zu debuggen und zu parallelisieren.
2. Unveränderlichkeit (Immutability)
In der FP sind Daten standardmäßig unveränderlich. In C# werden dafür verwendet:
- Das Schlüsselwort
readonlyfür Felder. record-Strukturen (C# 9+), die automatisch unveränderliche Objekte erstellen.- Sammlungen aus dem Namespace
System.Collections.Immutable.
Beispiel mit record:
public record Person(string Name, int Age);Wenn das Alter geändert werden muss, wird eine neue Instanz erstellt:
Person person = new("Ivan", 30);Person olderPerson = person with { Age = 31 };Unveränderlichkeit verhindert versehentliche Mutationen und vereinfacht die Multithreading-Programmierung.
3. Funktionen höherer Ordnung und Lambda-Ausdrücke
Funktionen höherer Ordnung nehmen andere Funktionen als Argumente entgegen oder geben sie zurück. In C# wird dies durch Delegaten realisiert: Func<>, Action<>, Predicate<>.
Ein einfaches Beispiel ist die Where-Methode aus LINQ:
List<int> numbers = new() { 1, 2, 3, 4, 5 };var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();Hier ist n => n % 2 == 0 ein Lambda-Ausdruck (eine anonyme Funktion).
Man kann seine eigene Funktion höherer Ordnung schreiben:
public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate){ var result = new List<T>(); foreach (var item in list) if (predicate(item)) result.Add(item); return result;}4. LINQ: Ein mächtiges Werkzeug des funktionalen Stils
LINQ (Language Integrated Query) ist wohl das bekannteste Beispiel für funktionale Programmierung in C#. Es ermöglicht die deklarative Verarbeitung von Sammlungen, ähnlich wie in SQL.
Beispiel: Namen von Benutzern über 18 Jahren abrufen, sortiert nach Alter:
var users = new List<User>{ new("Anna", 22), new("Boris", 17), new("Viktor", 30)};
var result = users .Where(u => u.Age >= 18) .OrderBy(u => u.Age) .Select(u => u.Name) .ToList();LINQ unterstützt verzögerte Ausführung (lazy evaluation), was Ressourcen spart.
5. Monaden und der Maybe-Typ in C#
Monaden sind eine Abstraktion für Berechnungsketten mit Kontext (z. B. Behandlung von null oder Asynchronität). C# hat keine eingebaute Maybe-Monade, aber sie ist leicht zu implementieren:
public class Maybe<T>{ private readonly T _value; public bool HasValue { get; } p