web-dev-qa-db-fra.com

Pourquoi utiliseriez-vous Expression <Func <T >> plutôt que Func <T>?

Je comprends lambdas et les délégués Func et Action. Mais les expressions me bouchent. Dans quelles circonstances utiliseriez-vous un Expression<Func<T>> plutôt qu'un simple ancien Func<T>?

888
Richard Nagle

Lorsque vous voulez traiter les expressions lambda comme des arbres d'expression et regarder à l'intérieur de celles-ci au lieu de les exécuter. Par exemple, LINQ to SQL obtient l'expression et la convertit en instruction SQL équivalente et la soumet au serveur (plutôt que d'exécuter le lambda).

Conceptuellement, Expression<Func<T>> est complètement différent ​​de Func<T>. Func<T> désigne une delegate qui est en gros un pointeur sur une méthode et Expression<Func<T>> désigne une structure de données en arborescence pour une expression lambda. Cette arborescence décrit ce que fait une expression lambda plutôt que de faire la chose réelle. Il contient essentiellement des données sur la composition d'expressions, de variables, d'appels de méthodes, ... (par exemple, il contient des informations telles que ce lambda est une constante + un paramètre). Vous pouvez utiliser cette description pour la convertir en méthode réelle (avec Expression.Compile]) ou effectuer d'autres opérations (comme l'exemple LINQ to SQL). Traiter les lambdas comme des méthodes et des arbres d'expression anonymes est purement une tâche de compilation.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

compilera efficacement en une méthode IL qui n’obtient rien et retourne 10.

Expression<Func<int>> myExpression = () => 10;

sera converti en une structure de données qui décrit une expression qui n’obtient aucun paramètre et renvoie la valeur 10:

Expression vs Func image plus grande

Bien qu'ils se ressemblent tous les deux à la compilation, le compilateur génère un comportement totalement différent .

1079
Mehrdad Afshari

J'ajoute une réponse pour noobs parce que ces réponses me paraissaient insensées, jusqu'à ce que je réalise à quel point c'est simple. Parfois, vous vous attendez à ce que ce soit compliqué, ce qui vous empêche de "comprendre".

Je n'avais pas besoin de comprendre la différence jusqu'à ce que je tombe dans un 'bogue' vraiment ennuyeux, essayant d'utiliser LINQ-to-SQL de manière générique:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

Cela a très bien fonctionné jusqu'à ce que je commence à obtenir OutofMemoryExceptions sur des jeux de données plus volumineux. Le fait de définir des points d'arrêt à l'intérieur du lambda m'a fait comprendre qu'il parcourait chaque rangée de mon tableau, un par un, à la recherche des correspondances correspondant à mon état lambda. Cela m'a stoppé pendant un moment, parce que pourquoi diable traite-t-il ma table de données comme un IEnumerable géant au lieu de faire LINQ-to-SQL comme il est supposé? Il faisait également exactement la même chose dans mon homologue LINQ-to-MongoDb.

Le correctif consistait simplement à transformer Func<T, bool> en Expression<Func<T, bool>>, alors j'ai cherché sur Google pourquoi il avait besoin d'un Expression au lieu de Func, pour arriver ici.

ne expression transforme simplement un délégué en une donnée sur elle-même. Donc a => a + 1 devient quelque chose du type "Sur le côté gauche, il y a un int a. Sur le côté droit, vous ajoutez 1 . " C'est tout. Tu peux rentrer chez toi maintenant. C’est évidemment plus structuré que cela, mais c’est essentiellement tout l’arbre d’expression qui est réellement - rien qui puisse vous envelopper la tête.

En comprenant cela, il devient clair pourquoi LINQ-to-SQL requiert un Expression, et un Func ne convient pas. Func ne contient pas un moyen d'entrer dans lui-même, de voir le détail de la façon de le traduire en une requête SQL/MongoDb/other. Vous ne pouvez pas voir s'il s'agit de l'addition, de la multiplication ou de la soustraction. Tout ce que vous pouvez faire, c'est l'exécuter. Expression, en revanche, vous permet de regarder à l'intérieur du délégué et de voir tout ce qu'il veut faire. Cela vous permet de traduire le délégué en tout ce que vous voulez, comme une requête SQL. Func n'a pas fonctionné car mon DbContext était aveugle au contenu de l'expression lambda. De ce fait, il ne pouvait pas transformer l'expression lambda en SQL; Cependant, il a fait la meilleure chose suivante et a répété cela conditionnellement dans chaque ligne de mon tableau.

Edit: exposant ma dernière phrase à la demande de John Peter:

IQueryable étend IEnumerable. Par conséquent, les méthodes de IEnumerable telles que Where() obtiennent des surcharges acceptant Expression. Lorsque vous passez un Expression à cela, vous conservez un IQueryable, mais lorsque vous passez un Func, vous tombez sur la base IEnumerable et vous obtenez un IEnumerable. . En d'autres termes, sans vous rendre compte que vous avez transformé votre jeu de données en une liste à itérer, par opposition à une requête. Il est difficile de remarquer une différence tant que les signatures ne sont pas cachées.

296
Chad Hedgcock

Un facteur extrêmement important dans le choix de Expression vs Func est que les fournisseurs IQueryable tels que LINQ to Entities peuvent "digérer" ce que vous transmettez dans une expression, mais ignoreront ce que vous transmettez dans un Func. J'ai deux articles de blog sur le sujet:

Plus d'informations sur Expression vs Func avec Entity Framework et Tomber amoureux de LINQ - Partie 7: Expressions et Funcs (dernière section)

96
LSpencer777

J'aimerais ajouter quelques notes sur les différences entre Func<T> et Expression<Func<T>>:

  • Func<T> n'est qu'un MulticastDelegate de vieille école normal;
  • Expression<Func<T>> est une représentation de l'expression lambda sous la forme d'un arbre d'expression;
  • l'arbre d'expression peut être construit à l'aide de la syntaxe d'expression lambda ou de la syntaxe API.
  • l'arbre d'expression peut être compilé en délégué Func<T>;
  • la conversion inverse est théoriquement possible, mais c'est une sorte de décompilation, il n'y a pas de fonctionnalité intégrée pour cela car ce n'est pas un processus simple;
  • l'arbre d'expression peut être observé/traduit/modifié à l'aide de la variable ExpressionVisitor;
  • les méthodes d'extension pour IEnumerable fonctionnent avec Func<T>;
  • les méthodes d'extension pour IQueryable fonctionnent avec Expression<Func<T>>.

Il y a un article qui décrit les détails avec des exemples de code:
LINQ: Func <T> vs. Expression <Func <T >> .

J'espère que ça vous sera utile.

72

Le livre de Krzysztof Cwalina fournit une explication plus philosophique à ce sujet (. Directives pour la conception de cadres: conventions, idiomes et modèles pour les bibliothèques .NET réutilisables );

Rico Mariani

Éditer pour une version non-image:

La plupart du temps, vous voudrez Func ou Action si tout ce qui doit se passer est d’exécuter du code. Vous avez besoin de Expression lorsque le code doit être analysé, sérialisé ou optimisé avant d'être exécuté. Expression sert à réfléchir au code, Func/Action sert à l'exécuter .

64
Oğuzhan Soykan

LINQ est l'exemple canonique (par exemple, parler à une base de données), mais en vérité, chaque fois que vous vous souciez plus d'exprimer ce que doit faire, plutôt que en train de le faire. Par exemple, j'utilise cette approche dans la pile RPC de protobuf-net (pour éviter la génération de code, etc.) - vous appelez donc une méthode avec:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

Cela déconstruit l'arborescence des expressions pour résoudre SomeMethod (et la valeur de chaque argument), effectue l'appel RPC, met à jour les arguments ref/out et renvoie le résultat de l'appel distant. Ceci n'est possible que via l'arbre d'expression. Je couvre ce plus ici .

Un autre exemple est lorsque vous construisez manuellement les arbres d'expression dans le but de les compiler en lambda, comme le fait le code opérateurs génériques .

37
Marc Gravell

Vous utiliseriez une expression lorsque vous souhaitez traiter votre fonction en tant que données et non en tant que code. Vous pouvez le faire si vous souhaitez manipuler le code (en tant que données). La plupart du temps, si vous ne voyez pas le besoin d'expressions, vous n'avez probablement pas besoin de les utiliser.

19
Andrew Hare

La raison principale est que vous ne voulez pas exécuter le code directement, mais plutôt l'inspecter. Cela peut être pour plusieurs raisons:

  • Mappage du code sur un environnement différent (code C # vers SQL dans Entity Framework)
  • Remplacement de parties du code en cours d'exécution (programmation dynamique ou même techniques DRY simples]
  • Validation du code (très utile pour émuler un script ou pour effectuer une analyse)
  • Sérialisation - les expressions peuvent être sérialisées assez facilement et en toute sécurité, les délégués ne peuvent pas
  • Sécurité fortement typée sur des éléments qui ne le sont pas intrinsèquement et exploitation des vérifications du compilateur même si vous effectuez des appels dynamiques au moment de l'exécution (ASP.NET MVC 5 avec Razor en est un bel exemple)
17
Luaan

Je ne vois pas encore de réponses qui mentionnent la performance. Passer Func<>s dans Where() ou Count() est incorrect. Vraiment mauvais Si vous utilisez un Func<>, il appelle le fichier IEnumerable LINQ au lieu de IQueryable, ce qui signifie que des tables entières sont extraites et alors filtrées. Expression<Func<>> est nettement plus rapide, notamment si vous interrogez une base de données hébergeant un autre serveur.

11
mhenry1384