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>
?
c#
delegates
lambda
expression-trees
Richard Nagle
la source
la source
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 deFunc<T>
.Func<T>
dénote undelegate
qui est à peu près un pointeur vers une méthode etExpression<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 (avecExpression.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.compilera efficacement vers une méthode IL qui n'obtient rien et renvoie 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:
une plus grande image
Bien qu'ils se ressemblent tous les deux au moment de la compilation, ce que le compilateur génère est totalement différent .
la source
Expression
contient les méta-informations sur un certain délégué.Expression<Func<...>>
au lieu de simplementFunc<...>
.(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:
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>
devenirExpression<Func<T, bool>>
, alors j'ai cherché pourquoi il fallait unExpression
au lieu deFunc
, 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 unint 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 unFunc
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 acceptentExpression
. Lorsque vous passez unExpression
à cela, vous conservez un IQueryable en conséquence, mais lorsque vous passez unFunc
, 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.la source
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)
la source
Je voudrais ajouter quelques notes sur les différences entre
Func<T>
etExpression<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.
la source
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 source
database.data.Where(i => i.Id > 0)
être exécuté en tant queSELECT 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 estSELECT *
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 leFunc
dansExpression
responsabilise le pilote pour analyser leFunc
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:
Cela déconstruit l'arborescence d'expressions à résoudre
SomeMethod
(et la valeur de chaque argument), effectue l'appel RPC, met à jour toutref
/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 .
la source
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 source
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:
la source
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<>>
la source