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


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


771
2018-04-27 13:50


origine


Réponses:


Lorsque vous voulez traiter les expressions lambda comme des arbres d'expression et les regarder à l'intérieur au lieu de les exécuter. Par exemple, LINQ to SQL obtient l'expression et la convertit en l'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énote un delegate ce qui est à peu près un indicateur d'une méthode et Expression<Func<T>> dénote un structure de données d'arbre 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 des expressions, des variables, des 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 faire d'autres choses (comme l'exemple LINQ to SQL) avec. Le fait de traiter les lambdas comme des méthodes anonymes et des arbres d'expression est purement une question de temps de compilation.

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

compilera efficacement à une méthode IL qui ne reçoit rien et renvoie 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

Alors qu'ils se ressemblent tous les deux au moment de la compilation, ce que le compilateur génère est totalement différent.


956
2018-04-27 13:52



J'ajoute une réponse-pour-noobs parce que ces réponses m'ont semblé sur la tête, 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 vous en passer la tête.

Je n'avais pas besoin de comprendre la différence avant d'entrer dans un «bogue» vraiment ennuyant en 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 fonctionné très bien jusqu'à ce que je commence à obtenir OutofMemoryExceptions sur des ensembles de données plus volumineux. La mise en place de points d'arrêt à l'intérieur du lambda m'a fait réaliser qu'il parcourait chaque rang de ma table un par un à la recherche de correspondances à mon état de lambda. Cela m'a bloqué pendant un moment, car pourquoi diable traite-t-on ma table de données comme un IEnumerable géant au lieu de faire LINQ-to-SQL comme il est censé le faire? Il faisait aussi exactement la même chose dans mon homologue LINQ-to-MongoDb.

La solution était simplement de tourner Func<T, bool> dans Expression<Func<T, bool>>, donc j'ai googlé pourquoi il a besoin d'un Expression au lieu de Func, se terminant ici.

Une expression transforme simplement un délégué en une donnée sur lui-même. Alors a => a + 1 devient quelque chose comme "Sur le côté gauche, il y a un int a. Sur le côté droit, vous en ajoutez 1. " C'est tout. Vous pouvez rentrer à la maison maintenant. C'est évidemment plus structuré que ça, mais c'est essentiellement tout un arbre d'expression - rien qui vous entoure.

Comprendre cela, il devient clair pourquoi LINQ-to-SQL a besoin d'un Expressionet un Func n'est pas adéquat. Func ne comporte pas un moyen d'entrer dans lui-même, de voir le nitty-gritty de la façon de le traduire en une requête SQL / MongoDb / autre. Vous ne pouvez pas voir s'il effectue une addition ou une multiplication sur la soustraction. Tout ce que vous pouvez faire est de l'exécuter. Expression, d'autre part, vous permet de regarder à l'intérieur du délégué et de voir tout ce qu'il veut faire, vous permettant de le traduire en ce que vous voulez, comme une requête SQL. Func n'a pas fonctionné parce que mon DbContext était aveugle à ce qui était réellement dans l'expression de lambda pour le transformer en SQL, ainsi il a fait la meilleure chose suivante et itéré que conditionnel à travers chaque rangée dans ma table.

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

IQueryable étend IEnumerable, donc les méthodes de IEnumerable comme Where() obtenir des surcharges qui acceptent Expression. Quand vous passez un Expression à cela, vous gardez un IQueryable en conséquence, mais quand vous passez un Func, vous retombez sur la base IEnumerable et vous obtiendrez un IEnumerable comme résultat. En d'autres termes, sans vous en rendre compte, vous avez transformé votre ensemble de données en une liste à lire plutôt que quelque chose à interroger. Il est difficile de remarquer une différence jusqu'à ce que vous regardiez vraiment sous le capot aux signatures.


196
2018-01-05 08:04



Une considération extrêmement importante dans le choix d'Expression vs Func est que les fournisseurs IQueryable comme LINQ to Entities peuvent 'digérer' ce que vous passez dans une Expression, mais ignoreront ce que vous passez dans un Func. J'ai deux billets de blog sur le sujet:

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


91
2018-01-11 15:57



Je voudrais ajouter quelques notes sur les différences entre Func<T> et Expression<Func<T>>:

  • Func<T> est juste un MulticastDelegate old-school normal;
  • Expression<Func<T>> est une représentation de l'expression de lambda sous forme d'arbre d'expression;
  • l'arbre d'expression peut être construit par la syntaxe d'expression lambda ou par la syntaxe de l'API;
  • arbre d'expression peut être compilé à un 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;
  • arbre d'expression peut être observé / traduit / modifié à travers le 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> et Expression <Func <T >>.

J'espère que cela sera utile.


60
2018-06-11 00:02



Il y a une explication plus philosophique à ce sujet du livre de Krzysztof Cwalina (Directives de conception de cadre: conventions, expressions idiomatiques et modèles pour les bibliothèques .NET réutilisables)

Rico Mariani

Modifier pour la version sans image:

La plupart du temps, vous allez vouloir Func ou action si tout ce qui doit arriver est d'exécuter du code. Vous avez besoin Expression lorsque le code doit être analysé, sérialisé ou optimisé avant d'être exécuté. Expression est de penser au code, Func / Action est pour l'exécuter.


40
2018-03-11 13:10



LINQ est l'exemple canonique (par exemple, en parlant à une base de données), mais en vérité, chaque fois que vous vous souciez plus de l'expression quelle faire, plutôt que de le faire réellement. 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'arbre d'expression à résoudre SomeMethod (et la valeur de chaque argument), effectue l'appel RPC, met à jour ref/out args et renvoie le résultat de l'appel distant. Ceci n'est possible que via l'arbre d'expression. Je couvre cela plus ici.

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


33
2018-04-27 14:13



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


18
2018-04-27 13:53



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

  • Mappage du code à un environnement différent (par exemple, du code C # à SQL dans Entity Framework)
  • Remplacer des parties du code en cours d'exécution (programmation dynamique ou même techniques DRY simples)
  • Validation de code (très utile lors de l'émulation de scripts ou lors de l'analyse)
  • Sérialisation - les expressions peuvent être sérialisées plutôt facilement et en toute sécurité, les délégués ne peuvent pas
  • Sécurité fortement typée sur des choses qui ne sont pas intrinsèquement fortement typées, et exploitant les vérifications du compilateur même si vous faites des appels dynamiques en cours d'exécution (ASP.NET MVC 5 avec Razor est un bon exemple)

15
2018-03-26 12:54



Je ne vois pas encore de réponses qui mentionnent la performance. Qui passe Func<>s dans Where() ou Count() est mauvais. Vraiment mauvais. Si vous utilisez un Func<> alors il appelle le IEnumerable LINQ trucs au lieu de IQueryable, ce qui signifie que des tables entières sont tirées et puis filtré. Expression<Func<>> est significativement plus rapide, surtout si vous interrogez une base de données qui vit sur un autre serveur.


5
2018-06-16 15:58