Expression<Func<T>> vs Func<T>: Das Geheimnis der Optimierung in C#

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Expression<Func<T>> vs Func<T>: Was steckt hinter dieser Frage auf Stack Overflow?

\n\n

Stellen Sie sich vor: Sie schreiben C#-Code. Sie müssen eine Filterbedingung an eine Methode übergeben. Was wählen Sie: Func<User, bool> oder Expression<Func<User, bool>>? Der Unterschied scheint minimal – beide nehmen einen Benutzer entgegen und geben bool zurück. Aber hinter den Kulissen verbirgt sich ein ganzes Universum an Unterschieden.

\n\n

Auf Stack Overflow hat diese Frage tausende Aufrufe erhalten – und das zu Recht. Heute werden wir verstehen, warum Expression<Func<T>> mächtiger, schneller und sicherer sein kann als ein gewöhnlicher Func<T>. Und vor allem: wann man welchen verwendet, um sich nicht ins eigene Bein zu schießen.

\n\n

Kurze Einführung: Was sind Func und Expression?

\n\n

Func<T> ist einfach ein Delegat. Eine ausführbare Funktion, kompilierter Maschinencode. Sie übergeben Daten – er verarbeitet sie. Schnell, effizient, aber … undurchsichtig.

\n\n
Func<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // wird sofort ausgeführt
\n\n

Expression<Func<T>> ist ein Ausdrucksbaum. Kein Code, sondern seine Beschreibung. Sie können ihn nicht einfach aufrufen – Sie müssen ihn entweder in einen Delegaten kompilieren (über .Compile()) oder seine Struktur analysieren.

\n\n
Expression<Func<User, bool>> expr = user => user.Age >= 18;\n// Kann nicht einfach aufgerufen werden: expr(someUser) – Kompilierungsfehler\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // jetzt möglich
\n\n

Scheinbar unnötiger Aufwand? Aber genau diese „Beschreibbarkeit“ öffnet die Tür zur Magie.

\n\n

Szenario 1: LINQ to Objects (Arbeit im Arbeitsspeicher)

\n\n

Wenn Sie mit Sammlungen im Arbeitsspeicher arbeiten (z. B. List<T>), verwendet LINQ to Objects Func<T>. Ausdrucksbäume werden hier nicht benötigt – alles wird im selben Prozess ausgeführt.

\n\n
var users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>
\n\n

Die Leistung ist hervorragend: keine zusätzlichen Kosten für die Analyse des Baums. Wenn Sie nur mit lokalen Daten arbeiten – verwenden Sie ruhig Func. Es ist schneller und einfacher.

\n\n

Szenario 2: LINQ to SQL / Entity Framework (Arbeit mit der Datenbank)

\n\n

Und hier beginnt die Magie. Wenn Sie schreiben:

\n\n
var adults = dbContext.Users.Where(u => u.Age >= 18).ToList();
\n\n

Entity Framework führt Ihren C#-Code nicht aus. Stattdessen analysiert es den übergebenen Ausdrucksbaum und wandelt ihn in eine SQL-Abfrage um:

\n\n
SELECT * FROM Users WHERE Age >= 18
\n\n

Wenn Sie Func übergeben würden, könnte EF nicht „hineinschauen“ – es würde einfach ALLE Benutzer aus der Tabelle laden und die Filterung im Arbeitsspeicher durchführen. Stellen Sie sich vor: eine Tabelle mit 10 Millionen Datensätzen – und alle werden über das Netzwerk in Ihre Anwendung übertragen, um clientseitig gefiltert zu werden.

\n\n

Expression<Func<T>> ermöglicht es EF (oder jedem anderen IQueryable-Anbieter), Ihre Bedingung zu lesen, in die Sprache der Datenbank zu übersetzen (SQL, MongoDB Query, OData usw.) und die Filterung auf dem Server durchzuführen.

\n\n

Leistung: Benchmarks und reale Zahlen

\n\n

Schauen wir uns die Zahlen an. Nehmen wir ein einfaches Beispiel: Filterung von 100.000 Benutzern nach Alter.

\n\n
// Variante 1: Func – alle laden, im Arbeitsspeicher filtern\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// Variante 2: Expression – auf dem Server filtern\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18
\n\n

Ergebnisse (ungefähr, aber aussagekräftig):

\n
    \n

Blogs

Buchempfehlungen