Pourquoi utiliser Expression> plutôt que Func?
je comprends lambdas et les délégués Func
et Action
. Mais les expressions me déconcertent. Dans quelles circonstances utiliseriez-vous un Expression<Func<T>>
plutôt qu'un simple Func<T>
?
9 réponses
quand vous voulez traiter les expressions lambda comme des arbres d'expression et regarder à l'intérieur d'eux au lieu de les exécuter. Par exemple, LINQ to SQL obtient l'expression et la convertit en déclaration SQL équivalente et la soumet au serveur (plutôt que d'exécuter la lambda).
conceptuellement, Expression<Func<T>>
est complètement différent de Func<T>
. Func<T>
dénote un delegate
qui est à peu près un pointeur vers une méthode et Expression<Func<T>>
dénote une tree data structure pour une expression lambda. Cette structure d'arbre décrit ce qu'une expression lambda fait 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 comme cette lambda est une constante + un paramètre). Vous pouvez utiliser cette description pour la convertir à une méthode réelle (avec Expression.Compile
) ou faire d'autres des trucs (comme le LINQ to SQL exemple). Le fait de traiter lambdas comme des méthodes anonymes et des arbres d'expression est purement une chose de temps de compilation.
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
compilera effectivement à 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 reçoit aucun paramètre et renvoie la valeur 10:
alors qu'ils se ressemblent tous les deux au moment de la compilation, ce que le compilateur génère est totalement différent .
j'ajoute une réponse-pour-noobs parce que ces réponses me semblaient au-dessus de la tête, jusqu'à ce que je réalise à quel point c'est simple. Parfois, c'est votre attente, que c'est compliqué qui vous rend incapable de 'envelopper votre tête autour de lui".
Je n'avais pas besoin de comprendre la différence jusqu'à ce que je tombe dans un 'bug' vraiment ennuyeux 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 bien fonctionné jusqu'à ce que je commence à sortir de la plupart des exceptions sur plus grands ensembles de données. Mettre des points d'arrêt à l'intérieur de la lambda m'a fait réaliser qu'il était itérating à travers chaque ligne dans ma table un-par-un à la recherche de correspondances à ma condition lambda. Ça m'a laissé un moment sans réponse, parce que pourquoi diable traite-t-il ma table de données comme un énorme IEnumerable au lieu de faire du LINQ-to-SQL comme c'est supposé le faire? Il faisait exactement la même chose dans mon homologue de LINQ-to-MongoDb.
le correctif était simplement de transformer Func<T, bool>
en Expression<Func<T, bool>>
, donc J'ai cherché sur Google pourquoi il faut un Expression
au lieu de Func
, pour finir ici.
une expression transforme simplement un délégué en une donnée sur lui-même. donc 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 ça. vous pouvez rentrer chez vous. Il est évidemment plus structuré que cela, mais c'est essentiellement tout un arbre d'expression est vraiment--rien à envelopper autour de votre tête.
comprendre que , il devient clair pourquoi LINQ-to-SQL a besoin d'un Expression
, et un Func
n'est pas adéquat. Func
ne porte pas avec elle un moyen d'entrer en elle-même, pour 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 fait l'addition ou la multiplication sur la soustraction. Tout ce que tu peux faire, c'est le lancer. Expression
, d'autre part, vous permet de regarder à l'intérieur du délégué et de tout voir il veut le faire, vous donnant le pouvoir 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 lambda pour le transformer en SQL, donc il a fait la meilleure chose suivante et itéré que conditionnel à travers chaque ligne dans ma table.
Edit: exposer sur ma dernière phrase à Jean-Pierre demande:
IQueryable étend IEnumerable, donc IEnumerable des méthodes 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 tombez en arrière sur la base IEnumerable et vous obtiendrez un IEnumerable en conséquence. En d'autres termes, sans vous en rendre compte, vous avez transformé votre ensemble de données en une liste à itérer plutôt qu'en quelque chose à interroger. C'est dur de remarquer une différence jusqu'à ce que vous regardiez vraiment sous le capot les signatures.
une considération extrêmement importante dans le choix de L'Expression vs Func est que les fournisseurs Iqueryables comme LINQ to Entities peuvent "digérer" ce que vous passez dans une Expression, mais ignoreront ce que vous passez dans une Func. J'ai deux billets de blog sur le sujet:
pour en savoir plus sur Expression vs Func avec Entity Framework et Tomber en Amour avec LINQ - Partie 7: Expressions et Funcs (la dernière partie)
j'aimerais ajouter quelques notes sur les différences entre Func<T>
et Expression<Func<T>>
:
-
Func<T>
n'est qu'un délégué normal de la vieille école; -
Expression<Func<T>>
est une représentation de l'expression lambda sous forme d'arbre d'expression; - expression de l'arbre peut être construit à travers lambda expression de syntaxe ou par le biais de l'API de la syntaxe;
- arbre d'expression peut être compilé pour un délégué
Func<T>
; - la conversion inverse est théoriquement possible, mais c'est une sorte de décomposition, il n'y a pas de fonctionnalité intégrée pour cela car ce n'est pas un processus simple;
- expression de l'arbre peut être observée/traduit/modifié par le biais de la
ExpressionVisitor
; - les méthodes d'extension pour IEnumerable fonctionnent avec
Func<T>
; - les méthodes d'extension pour IQueryable utiliser
Expression<Func<T>>
.
il y a un article qui décrit les détails avec des échantillons de code:
LINQ: Func < T> vs. Expression
espérons qu'il sera utile.
il y a une explication plus philosophique à ce sujet dans le livre de Krzysztof Cwalina ( Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable. net Libraries );
Modifier pour la version non-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 de Expression quand le code doit être analysé, sérialisé, ou optimisé avant qu'il ne soit lancé. Expression pour penser au code, Func / Action pour l'exécuter.
LINQ est l'exemple canonique (par exemple, parler à une base de données), mais en vérité, chaque fois que vous vous souciez davantage d'exprimer ce que à 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) - donc vous appelez une méthode avec:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
cela déconstruit l'arbre des expressions pour résoudre SomeMethod
(et la valeur de chaque argument), effectue l'appel RPC, met à jour n'importe quel ref
/ out
args, et renvoie le résultat de l'appel à distance. Ceci n'est possible que via l'arbre des expressions. Je couvre ce plus ici .
un autre exemple est lorsque vous construisez les arbres d'expression manuellement dans le but de compiler à un lambda, comme fait par le opérateurs génériques code.
vous utiliserez une expression lorsque vous voulez traiter votre fonction comme des données et non comme du code. Vous pouvez le faire si vous voulez manipuler le code (de données). La plupart du temps, si vous n'avez pas besoin d'expressions, vous n'avez probablement pas besoin d'en utiliser.
la raison principale est quand vous ne voulez pas exécuter le code directement, mais plutôt, vouloir l'inspecter. Cela peut être pour toutes sortes de raisons:
- la Cartographie du code dans un autre environnement (ie. Le code C# SQL dans le Cadre de l'Entité)
- de Remplacer des parties du code d'exécution (programmation dynamique ou même de la plaine SÈCHE techniques) Validation du Code
- (très utile lors de l'émulation de scripts ou lors de l'analyse)
- Sérialisation - expressions peuvent être sérialisés 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 par nature fortement typées, et Exploitation des vérifications de compilateurs même si vous faites des appels dynamiques dans l'exécution (ASP.NET MVC 5 avec rasoir est un bel exemple)
Je ne vois pas encore de réponses qui mentionnent la performance. Passer Func<>
s à Where()
ou Count()
est mauvais. Vraiment mauvais. Si vous utilisez un Func<>
, alors il appelle le IEnumerable
LINQ stuff au lieu de IQueryable
, ce qui signifie que des tables entières sont tirées et puis filtré. Expression<Func<>>
est beaucoup plus rapide, surtout si vous interrogez une base de données qui vit sur un autre serveur.