Expression\n\nImaginez : vous écrivez du code C#. Vous devez passer une condition de filtrage à une méthode. Que choisir : Func<User, bool> ou Expression<Func<User, bool>> ? La différence semble minime — les deux acceptent un utilisateur et retournent un bool. Mais en coulisses se cache tout un univers de différences.
\n\nSur Stack Overflow, cette question a recueilli des milliers de vues — et pour cause. Aujourd'hui, nous allons comprendre pourquoi Expression<Func<T>> peut être plus puissant, plus rapide et plus sûr qu'un simple Func<T>. Et surtout, quand utiliser chacun d'eux pour ne pas se tirer une balle dans le pied.
\n\nPetit rappel : qu'est-ce que Func et Expression ?
\n\nFunc<T> est simplement un délégué. Une fonction prête à être exécutée, du code machine compilé. Vous lui passez des données — elle les traite. Rapide, efficace, mais… opaque.
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // s'exécute instantanément
\n\nExpression<Func<T>> est un arbre d'expression. Pas du code, mais sa description. Vous ne pouvez pas simplement l'appeler — il faut soit le compiler en délégué (via .Compile()), soit analyser sa structure.
\n\nExpression<Func<User, bool>> expr = user => user.Age >= 18;\n// Impossible d'appeler simplement : expr(someUser) — erreur de compilation\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // maintenant c'est possible
\n\nPourquoi tant d'efforts supplémentaires ? Mais c'est précisément ce caractère « descriptif » qui ouvre la porte à la magie.
\n\nScénario 1 : LINQ to Objects (travail en mémoire)
\n\nLorsque vous travaillez avec des collections en mémoire (par exemple, List<T>), LINQ to Objects utilise Func<T>. Les arbres d'expression ne sont pas nécessaires ici — tout s'exécute dans le même processus.
\n\nvar users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>
\n\nLes performances sont excellentes : aucun frais général lié à l'analyse de l'arbre. Si vous travaillez uniquement avec des données locales — utilisez Func en toute confiance. Il est plus rapide et plus simple.
\n\nScénario 2 : LINQ to SQL / Entity Framework (travail avec une base de données)
\n\nEt c'est là que la magie commence. Lorsque vous écrivez :
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();
\n\nEntity Framework n'exécute pas votre code C#. Au lieu de cela, il analyse l'arbre d'expression passé et le transforme en requête SQL :
\n\nSELECT * FROM Users WHERE Age >= 18
\n\nSi vous aviez passé un Func, EF n'aurait pas pu « regarder à l'intérieur » — il aurait simplement chargé TOUS les utilisateurs de la table et effectué le filtrage en mémoire. Imaginez : une table de 10 millions d'enregistrements — et tous transitent par le réseau vers votre application pour être filtrés côté client.
\n\nExpression<Func<T>> permet à EF (ou à tout autre fournisseur IQueryable) de lire votre condition, de la traduire dans le langage de la base de données (SQL, requête MongoDB, OData, etc.) et d'effectuer le filtrage sur le serveur.
\n\nPerformances : benchmarks et chiffres réels
\n\nRegardons les chiffres. Prenons un exemple simple : le filtrage de 100 000 utilisateurs par âge.
\n\n// Option 1 : Func — on charge tout, on filtre en mémoire\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// Option 2 : Expression — on filtre sur le serveur\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18
\n\nRésultats (approximatifs mais révélateurs) :
\n\n
Imaginez : vous écrivez du code C#. Vous devez passer une condition de filtrage à une méthode. Que choisir : Func<User, bool> ou Expression<Func<User, bool>> ? La différence semble minime — les deux acceptent un utilisateur et retournent un bool. Mais en coulisses se cache tout un univers de différences.
Sur Stack Overflow, cette question a recueilli des milliers de vues — et pour cause. Aujourd'hui, nous allons comprendre pourquoi Expression<Func<T>> peut être plus puissant, plus rapide et plus sûr qu'un simple Func<T>. Et surtout, quand utiliser chacun d'eux pour ne pas se tirer une balle dans le pied.
\n\nPetit rappel : qu'est-ce que Func et Expression ?
\n\nFunc<T> est simplement un délégué. Une fonction prête à être exécutée, du code machine compilé. Vous lui passez des données — elle les traite. Rapide, efficace, mais… opaque.
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // s'exécute instantanément\n\nExpression<Func<T>> est un arbre d'expression. Pas du code, mais sa description. Vous ne pouvez pas simplement l'appeler — il faut soit le compiler en délégué (via .Compile()), soit analyser sa structure.
Expression<Func<User, bool>> expr = user => user.Age >= 18;\n// Impossible d'appeler simplement : expr(someUser) — erreur de compilation\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // maintenant c'est possible\n\nPourquoi tant d'efforts supplémentaires ? Mais c'est précisément ce caractère « descriptif » qui ouvre la porte à la magie.
\n\nScénario 1 : LINQ to Objects (travail en mémoire)
\n\nLorsque vous travaillez avec des collections en mémoire (par exemple, List<T>), LINQ to Objects utilise Func<T>. Les arbres d'expression ne sont pas nécessaires ici — tout s'exécute dans le même processus.
var users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>\n\nLes performances sont excellentes : aucun frais général lié à l'analyse de l'arbre. Si vous travaillez uniquement avec des données locales — utilisez Func en toute confiance. Il est plus rapide et plus simple.
\n\nScénario 2 : LINQ to SQL / Entity Framework (travail avec une base de données)
\n\nEt c'est là que la magie commence. Lorsque vous écrivez :
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();\n\nEntity Framework n'exécute pas votre code C#. Au lieu de cela, il analyse l'arbre d'expression passé et le transforme en requête SQL :
\n\nSELECT * FROM Users WHERE Age >= 18\n\nSi vous aviez passé un Func, EF n'aurait pas pu « regarder à l'intérieur » — il aurait simplement chargé TOUS les utilisateurs de la table et effectué le filtrage en mémoire. Imaginez : une table de 10 millions d'enregistrements — et tous transitent par le réseau vers votre application pour être filtrés côté client.
\n\nExpression<Func<T>> permet à EF (ou à tout autre fournisseur IQueryable) de lire votre condition, de la traduire dans le langage de la base de données (SQL, requête MongoDB, OData, etc.) et d'effectuer le filtrage sur le serveur.
\n\nPerformances : benchmarks et chiffres réels
\n\nRegardons les chiffres. Prenons un exemple simple : le filtrage de 100 000 utilisateurs par âge.
\n\n// Option 1 : Func — on charge tout, on filtre en mémoire\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// Option 2 : Expression — on filtre sur le serveur\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18\n\nRésultats (approximatifs mais révélateurs) :
\n- \n