Je comprends les lambdas et le Func
etAction
les délégués. Mais les expressions me frappent.
Dans quelles circonstances utiliseriez-vous Expression<Func<T>>
un ancien plutôt qu'un simple ancien Func<T>
?
Je comprends les lambdas et le Func
etAction
les délégués. Mais les expressions me frappent.
Dans quelles circonstances utiliseriez-vous Expression<Func<T>>
un ancien plutôt qu'un simple ancien Func<T>
?
Réponses:
Lorsque vous souhaitez traiter les expressions lambda comme des arborescences d'expressions et regarder à l'intérieur 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énote un delegate
qui est à peu près un pointeur vers une méthode et Expression<Func<T>>
dénote une structure de données d'arbre pour une expression lambda. Cette structure arborescente 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éthode, ... (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 une 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 chose au moment de la compilation.
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
compilera efficacement vers une méthode IL qui n'obtient 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:
Bien qu'ils se ressemblent tous les deux au moment de la compilation, ce que le compilateur génère est totalement différent .
Expression
contient les méta-informations sur un certain délégué.
Expression<Func<...>>
au lieu de simplement Func<...>
.
(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
telle expression est un ExpressionTree, des branches sont créées pour l'instruction If.
J'ajoute une réponse pour les noobs parce que ces réponses semblaient sur ma tête, jusqu'à ce que je réalise à quel point c'est simple. Parfois, vous vous attendez à ce que ce soit compliqué qui vous empêche de «vous envelopper la tête».
Je n'avais pas besoin de comprendre la différence jusqu'à ce que j'entre 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 ensembles de données plus importants. Définir des points d'arrêt à l'intérieur du lambda m'a fait réaliser qu'il parcourait chaque ligne de ma table un par un à la recherche de correspondances avec ma condition lambda. Cela m'a bloqué pendant un certain temps, car pourquoi diable traite-t-il ma table de données comme un géant IEnumerable au lieu de faire LINQ-to-SQL comme il est censé le faire? Il faisait également exactement la même chose dans mon homologue LINQ-to-MongoDb.
Le correctif était simplement de Func<T, bool>
devenir Expression<Func<T, bool>>
, alors j'ai cherché pourquoi il fallait un Expression
au lieu de Func
, pour finir ici.
Une expression transforme simplement un délégué en données sur lui-même.Devient a => a + 1
alors quelque chose comme "Sur le côté gauche, il y a un int a
. Sur le côté droit, vous ajoutez 1". C'est ça. Tu peux rentrer chez toi maintenant. C'est évidemment plus structuré que cela, mais c'est essentiellement tout ce qu'un arbre d'expression est vraiment - rien pour vous envelopper.
En comprenant cela, 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 lui un moyen d'entrer en lui-même, de voir le détail de la façon de le traduire en une requête SQL / MongoDb / autre. Vous ne pouvez pas voir s'il s'agit d'addition, de multiplication ou de soustraction. Tout ce que vous pouvez faire, c'est 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. Cela vous permet de traduire le délégué en 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. Pour cette raison, il n'a pas pu transformer l'expression lambda en SQL; cependant, il a fait la meilleure chose suivante et a itéré cette conditionnelle à travers chaque ligne de ma table.
Edit: exposant ma dernière phrase à la demande de John Peter:
IQueryable étend IEnumerable, donc les méthodes IEnumerable comme Where()
obtenir des surcharges qui acceptent Expression
. Lorsque vous passez un Expression
à cela, vous conservez un IQueryable en conséquence, mais lorsque vous passez un Func
, vous retombez sur le IEnumerable de base et vous obtiendrez un IEnumerable en conséquence. En d'autres termes, sans remarquer que vous avez transformé votre ensemble de données en liste à itérer plutôt qu'en quelque chose à interroger. Il est difficile 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 d'Expression vs Func est que les fournisseurs IQueryable comme LINQ to Entities peuvent `` digérer '' ce que vous passez dans une Expression, mais ignorent ce que vous passez dans un Func. J'ai deux articles de blog sur le sujet:
En savoir plus sur Expression vs Func avec Entity Framework et Falling in Love with LINQ - Partie 7: Expressions et Funcs (la dernière section)
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 lambda sous forme d'arbre d'expression;Func<T>
;ExpressionVisitor
;Func<T>
;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 ce 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 une version sans image:
La plupart du temps, vous voudrez Func ou Action si tout ce qui doit arriver est d'exécuter du code. Vous avez besoin d' Expression lorsque le code doit être analysé, sérialisé ou optimisé avant d'être exécuté. L'expression est pour penser au code, Func / Action est pour l'exécuter.
database.data.Where(i => i.Id > 0)
être exécuté en tant que SELECT FROM [data] WHERE [id] > 0
. Si vous passez juste un Func, vous avez mis des œillères sur votre pilote et tout ce qu'il peut faire est SELECT *
et une fois qu'il est chargé toutes ces données en mémoire, itérer à travers chaque filtre et à tout avec id> 0. Recouvrez le Func
dans Expression
responsabilise le pilote pour analyser le Func
et le transformer en une requête SQL / MongoDb / autre.
Expression
Func/Action
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 quoi 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'arborescence d'expressions à résoudre SomeMethod
(et la valeur de chaque argument), effectue l'appel RPC, met à jour tout ref
/ out
args et renvoie le résultat de l'appel distant. Cela n'est possible que via l'arbre d'expression. Je couvre cela plus ici .
Un autre exemple est lorsque vous créez manuellement les arborescences d'expression dans le but de les compiler en lambda, comme le fait le code des opérateurs génériques .
Vous utiliseriez une expression lorsque vous souhaitez traiter votre fonction comme des données et non comme du 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 d'en utiliser une.
La principale raison est que vous ne souhaitez pas exécuter le code directement, mais plutôt l'inspecter. Cela peut être pour un certain nombre de raisons:
Expression
peut être tout aussi impossible à sérialiser qu'un délégué, car toute expression peut contenir une invocation d'une référence arbitraire de délégué / méthode. "Facile" est bien sûr relatif.
Je ne vois pas encore de réponses qui mentionnent la performance. Passer ou Func<>
s est mauvais. Vraiment mauvais. Si vous utilisez un, il appelle la substance LINQ à la place de , ce qui signifie que des tables entières sont récupérées puis filtrées. est beaucoup plus rapide, surtout si vous interrogez une base de données qui vit sur un autre serveur.Where()
Count()
Func<>
IEnumerable
IQueryable
Expression<Func<>>