Expression<Func<T>> vs Func<T>: Was steckt hinter dieser Frage auf Stack Overflow?
\n\nStellen 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.
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\nKurze Einführung: Was sind Func und Expression?
\n\nFunc<T> ist einfach ein Delegat. Eine ausführbare Funktion, kompilierter Maschinencode. Sie übergeben Daten – er verarbeitet sie. Schnell, effizient, aber … undurchsichtig.
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // wird sofort ausgeführt\n\nExpression<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.
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\nScheinbar unnötiger Aufwand? Aber genau diese „Beschreibbarkeit“ öffnet die Tür zur Magie.
\n\nSzenario 1: LINQ to Objects (Arbeit im Arbeitsspeicher)
\n\nWenn 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.
var users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>\n\nDie 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\nSzenario 2: LINQ to SQL / Entity Framework (Arbeit mit der Datenbank)
\n\nUnd hier beginnt die Magie. Wenn Sie schreiben:
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();\n\nEntity Framework führt Ihren C#-Code nicht aus. Stattdessen analysiert es den übergebenen Ausdrucksbaum und wandelt ihn in eine SQL-Abfrage um:
\n\nSELECT * FROM Users WHERE Age >= 18\n\nWenn 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\nExpression<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\nLeistung: Benchmarks und reale Zahlen
\n\nSchauen 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\nErgebnisse (ungefähr, aber aussagekräftig):
\n- \n