Pour la plupart des développeurs, l'utilisation de l'arborescence d'expression est limitée aux expressions lambda dans LINQ. Souvent, nous n'accordons aucune importance au fonctionnement de la technologie «sous le capot».
Dans cet article, je vais vous montrer des techniques avancées pour travailler avec des arborescences d'expression: éliminer la duplication de code dans LINQ, génération de code, métaprogrammation, transpilation, automatisation des tests.
Vous apprendrez comment utiliser directement l'arbre d'expression, quels pièges la technologie a préparés et comment les contourner.

Sous la coupe - transcription vidéo et texte de
mon rapport avec DotNext 2018 Piter.
Je m'appelle Maxim Arshinov, je suis co-fondateur de la société d'externalisation du groupe Hi-Tech. Nous développons des logiciels pour les entreprises, et aujourd'hui je vais parler de la façon dont la technologie de l'arbre d'expression a été utilisée dans le travail quotidien et comment elle a commencé à nous aider.
Je n'ai jamais spécifiquement voulu étudier la structure interne des arbres d'expression, il semblait que c'était une sorte de technologie interne pour que l'équipe .NET pour LINQ fonctionne, et les programmeurs d'applications n'avaient pas besoin de connaître son API. Il s'est avéré qu'il y avait des problèmes appliqués qui devaient être résolus. Pour que la solution me plaise, j'ai dû monter dans l'intestin.
Toute cette histoire est étirée dans le temps, il y avait différents projets, différents cas. Quelque chose est sorti et je l'ai terminé, mais je vais me permettre de sacrifier la véracité historique au profit d'une présentation plus artistique, donc tous les exemples seront sur le même modèle de sujet - une boutique en ligne.

Imaginez que nous écrivons tous une boutique en ligne. Il a des produits et une coche "À vendre" dans le panneau d'administration. Nous afficherons uniquement les produits qui ont cette coche marquée sur la partie publique.

Nous prenons du DbContext ou du NHibernate, nous écrivons Where (), nous produisons IsForSale.
Tout va bien, mais les règles métier ne sont pas les mêmes pour que nous les écrivions une fois pour toutes. Ils évoluent avec le temps. Un gestionnaire vient et dit que nous devons toujours surveiller le solde et afficher uniquement les marchandises qui ont des soldes sur la partie publique, sans oublier la coche.

Nous ajoutons facilement une telle propriété. Maintenant que nos règles métier sont encapsulées, nous pouvons les réutiliser.

Essayons d'éditer LINQ. Tout va bien ici?
Non, cela ne fonctionnera pas, car IsAvailable ne correspond pas à la base de données, il s'agit de notre code et le fournisseur de requêtes ne sait pas comment l'analyser.

Nous pouvons lui dire que notre propriété a une telle histoire. Mais maintenant, ce lambda est dupliqué à la fois dans l'expression linq et dans la propriété.
Where(x => x.IsForSale && x.InStock > 0) IsAvailable => IsForSale && InStock > 0;
Donc, la prochaine fois que ce lambda changera, nous devrons faire Ctrl + Shift + F sur le projet. Naturellement, nous ne trouverons pas tous - des bogues et du temps. Je veux éviter ça.

Nous pouvons aller de ce côté et mettre une autre ToList () devant Where (). C'est une mauvaise décision, car s'il y a un million de marchandises dans la base de données, tout le monde monte en RAM et y filtre.

Si vous avez trois produits dans un magasin, la solution est bonne, mais dans le commerce électronique, il y en a généralement plus. Cela n'a fonctionné que parce que, malgré la similitude des lambdas entre eux, ils ont un type complètement différent. Dans le premier cas, il s'agit d'un délégué Func et dans le second, d'une arborescence d'expression. Il a la même apparence, les types sont différents, le bytecode est complètement différent.

Pour passer d'une expression à un délégué, il vous suffit d'appeler la méthode Compile (). Cette API fournit .NET: il y a une expression - compilée, a reçu un délégué.
Mais comment y retourner? Existe-t-il quelque chose dans .NET pour passer d'un délégué à des arborescences d'expressions? Si vous êtes familier avec LISP, par exemple, il existe un mécanisme de citation qui permet au code d'être interprété comme une structure de données, mais dans .NET, il ne l'est pas.
Express ou délégués?
Étant donné que nous avons deux types de lambdas, nous pouvons philosopher ce qui est primaire: l'arbre d'expression ou les délégués.
À première vue, la réponse est évidente: puisqu'il existe une merveilleuse méthode Compile (), l'arborescence d'expression est principale. Et nous devrions recevoir le délégué en compilant l'expression. Mais la compilation est un processus lent, et si nous commençons à le faire partout, nous obtenons une dégradation des performances. De plus, nous le recevrons dans des endroits aléatoires, où l'expression devait être compilée dans un délégué, il y aura une baisse des performances. Vous pouvez trouver ces endroits, mais ils affecteront le temps de réponse du serveur, et de manière aléatoire.

Par conséquent, ils doivent être mis en cache d'une manière ou d'une autre. Si vous avez écouté un exposé sur les structures de données simultanées, vous connaissez ConcurrentDictionary (ou vous en êtes tout simplement informé). Je vais omettre les détails sur les méthodes de mise en cache (avec des verrous, pas des verrous). C'est juste que ConcurrentDictionary a une méthode simple GetOrAdd (), et l'implémentation la plus simple est de la placer dans ConcurrentDictionary et de la mettre en cache. La première fois que nous obtenons la compilation, mais alors tout sera rapide, car le délégué a déjà été compilé.

Ensuite, vous pouvez utiliser une telle méthode d'extension, vous pouvez utiliser et refactoriser notre code avec IsAvailable (), décrire l'expression, compiler les propriétés IsAvailable () et l'appeler par rapport à l'objet actuel.
Il existe au moins deux packages qui implémentent cela:
Microsoft.Linq.Translations et
Signum Framework (
framework open source écrit par une société commerciale). Là et il y a à peu près la même histoire avec la compilation des délégués. API un peu différente, mais tout comme je l'ai montré sur la diapositive précédente.
Cependant, ce n'est pas la seule approche, et vous pouvez passer des délégués aux expressions. Depuis longtemps, il y a
un article sur le Habre à propos du Decompiler Délégué, où l'auteur affirme que toute compilation est mauvaise, car depuis longtemps.
En général, les délégués étaient avant les expressions, et vous pouvez y passer depuis les délégués. Pour ce faire, l'auteur utilise la méthodeBody.GetILAsByteArray (); de Reflection, qui renvoie vraiment tout le code IL de la méthode sous la forme d'un tableau d'octets. Si vous le mettez plus loin dans Reflection, vous pouvez obtenir une représentation objet de ce cas, le parcourir avec une boucle et construire un arbre d'expression. Ainsi, une transition inverse est également possible, mais elle doit être effectuée à la main.

Afin de ne pas parcourir toutes les propriétés, l'auteur suggère de suspendre l'attribut Calculé pour indiquer que cette propriété doit être insérée. Avant la demande, nous montons dans IsAvailable (), retirons son code IL, le convertissons en arborescence d'expression et remplaçons l'appel IsAvailable () par ce qui est écrit dans ce getter. Il s'avère un tel manuel en ligne.

Pour que cela fonctionne, avant de tout passer à ToList (), nous appelons la méthode spéciale Decompile (). Il fournit un décorateur pour l'original interrogeable et en ligne. Ce n'est qu'après cela que nous passons tout au fournisseur de requêtes, et tout va bien pour nous.

Le seul problème avec cette approche est que Delegate Decompiler 0.23.0 ne va pas avancer, il n'y a pas de support Core, et l'auteur lui-même dit que c'est un alpha profond, il y en a beaucoup qui sont inachevés, donc vous ne pouvez pas l'utiliser en production. Bien que nous reviendrons sur ce sujet.
Opérations booléennes
Il s'avère que nous avons résolu le problème de la duplication de conditions spécifiques.

Mais les conditions doivent souvent être combinées en utilisant la logique booléenne. Nous avions IsForSale (), InStock ()> 0, et entre eux la condition "ET". S'il y a une autre condition, ou une condition «OU» est requise.

Dans le cas de «And», vous pouvez tricher et vider tout le travail sur le fournisseur de requêtes, c'est-à-dire écrire beaucoup de Where () d'affilée, il sait comment le faire.

Si «OU» est requis, cela ne fonctionnera pas, car WhereOr () n'est pas dans LINQ et l'opérateur | | n'est pas surchargé d'expressions.
Spécifications techniques
Si vous connaissez le livre DDD d'Evans ou si vous connaissez simplement le modèle de spécification, c'est-à-dire un modèle de conception conçu spécifiquement pour cela. Il existe plusieurs règles métier et vous souhaitez combiner des opérations en logique booléenne - implémentez la spécification.

Une spécification est un tel terme, un ancien modèle de Java. Et en Java, en particulier dans l'ancien, il n'y avait pas de LINQ, donc il n'y a été implémenté que sous la forme de la méthode isSatisfiedBy (), c'est-à-dire uniquement des délégués, mais il n'a pas été question d'expressions. Il existe une implémentation sur Internet appelée
LinqSpecs , sur la diapositive vous la verrez. Je l'ai classé un peu pour moi, mais l'idée appartient à la bibliothèque.
Ici, tous les opérateurs booléens sont surchargés, les opérateurs vrai et faux sont surchargés de sorte que les deux opérateurs «&&» et «||» fonctionnent, sans eux une seule esperluette fonctionnera.

Ensuite, nous ajoutons des instructions implicites qui font que le compilateur suppose que la spécification est à la fois des expressions et des délégués. À tout endroit où Expression <> ou Func <> doit entrer dans la fonction, vous pouvez passer la spécification. Étant donné que l'opérateur implicite est surchargé, le compilateur analysera et remplacera les propriétés Expression ou IsSatisfiedBy.

IsSatisfiedBy () peut être implémenté en mettant en cache l'expression qui est venue. En tout cas, il s'avère que nous venons d'Expression, le délégué lui correspond, nous avons ajouté le support des opérateurs booléens. Maintenant, tout cela peut être arrangé. Les règles métier peuvent être mises dans des spécifications statiques, déclarées et combinées.
public static readonly Spec<Product> IsForSaleSpec = new Spec<Product>(x => x.IsForSale); public static readonly Spec<Product> IsInStockSpec = new Spec<Product>(x => x.InStock > 0);

Chaque règle métier n'est écrite qu'une seule fois, elle ne sera perdue nulle part, elle ne sera pas dupliquée, elles pourront être combinées. Les personnes venant au projet peuvent voir ce que vous avez, quelles conditions, comprendre le modèle sujet.

Il y a un petit problème: l'expression n'a pas les méthodes And (), Or () et Not (). Ce sont des méthodes d'extension, elles doivent être implémentées indépendamment.

Voici la première tentative de mise en œuvre. À propos de l'arborescence des expressions, il y a pas mal de documentation sur Internet, et tout n'est pas détaillé. Par conséquent, j'ai essayé de prendre Expression, j'ai appuyé sur Ctrl + Espace, j'ai vu OrElse (), lu à ce sujet. Passé deux expressions pour compiler et obtenir lambda. Cela ne fonctionnera pas.

Le fait est que cette expression se compose de deux parties: paramètre et corps. Le second est également constitué d'un paramètre et d'un corps. Dans OrElse (), vous devez passer les corps d'expressions, c'est-à-dire qu'il est inutile de comparer les lambdas avec "AND" et "OR", cela ne fonctionnera pas. Nous corrigeons, mais cela ne fonctionnera plus.
Mais si la dernière fois il y avait une exception NotSupportedException que le lambda n'était pas pris en charge, maintenant il y a une histoire étrange à propos du paramètre 1, paramètre 2, "quelque chose ne va pas, je ne travaillerai pas".
C # 7.0 en bref
Ensuite, j'ai pensé que la méthode de poke scientifique ne fonctionnerait pas, je dois le comprendre. Il a commencé à google et a trouvé le site du livre d'Albahari "
C # 7.0 en bref ".

Joseph Albahari, qui est également le développeur de la bibliothèque populaire LINQKit et LINQPad, vient de décrire ce problème. que vous ne pouvez pas simplement prendre et combiner l'expression, et si vous prenez l'expression magique.Invoke (), cela fonctionnera.
Question: qu'est-ce que Expression.Invoke ()? Accédez à nouveau à Google. Il crée une InvocationExpression qui applique une expression déléguée ou lambda à la liste d'arguments.

Si je vous lis ce code maintenant que nous prenons Expression.Invoke (), nous passons les paramètres, alors la même chose est écrite en anglais. Ça ne devient pas plus clair. Il y a de la magie Expression.Invoke () qui pour une raison quelconque résout ce problème avec les paramètres. Il faut croire, il n'est pas nécessaire de comprendre.

Dans le même temps, si vous essayez de nourrir les EF d'une telle expression combinée, il tombera à nouveau et dira que Expression.Invoke () n'est pas pris en charge. Soit dit en passant, EF Core a commencé à prendre en charge, mais EF 6 ne tient pas. Mais Albahari propose juste d'écrire AsExpandable (), et tout fonctionne.

Et vous pouvez remplacer dans les sous-requêtes d'expression où nous avons besoin d'un délégué. Pour les faire correspondre, nous écrivons Compile (), mais en même temps, si nous écrivons AsExpandable (), comme le suggère Albahari, cette Compile () ne se produira pas réellement, mais tout sera en quelque sorte magiquement fait correctement.

Je n'en ai pas cru un mot et suis monté dans la source. Qu'est-ce que la méthode AsExpandable ()? Il a une requête et QueryOptimizer. Nous laisserons le second hors des crochets, car il n'est pas intéressant, mais colle simplement Expression: s'il y a 3 + 5, il mettra 8.

Il est intéressant de noter que la méthode Expand () est appelée plus tard, après elle le queryOptimizer, puis tout est passé au fournisseur de requête en quelque sorte refait après la méthode Expand ().

Nous l'ouvrons, c'est Visitor, à l'intérieur, nous voyons le Compile () non original, qui compile autre chose. Je ne vous dirai pas quoi exactement, même si cela a une certaine signification, mais nous supprimons une compilation et la remplaçons par une autre. Génial, mais ça sent le marketing de niveau 80 parce que l'impact sur les performances ne va nulle part.
A la recherche d'une alternative
J'ai pensé que cela ne fonctionnerait pas et j'ai commencé à chercher une autre solution. Et je l'ai trouvé. Il y a un tel Pete Montgomery qui
écrit également sur ce problème et prétend qu'Albahari a truqué.

Pete a parlé avec les développeurs d'EF, et ils lui ont appris à tout combiner sans Expression.Evoke (). L'idée est très simple: l'embuscade était avec les paramètres. Le fait est qu'avec la combinaison Expression il y a un paramètre de la première expression et un paramètre de la seconde. Ils ne correspondent pas. Les corps étaient collés ensemble, mais les paramètres restaient suspendus dans l'air. Ils doivent être bandés de la bonne manière.
Pour ce faire, vous devez compiler un dictionnaire en examinant les paramètres des expressions, si le lambda ne provient pas d'un paramètre. Nous composons un dictionnaire, et nous lions à nouveau tous les paramètres du second aux paramètres du premier, de sorte que les paramètres initiaux entrent dans Expression, parcourent tout le corps que nous avons collé ensemble.

Une telle méthode simple vous permet de vous débarrasser de toutes les embuscades avec Expression.Invoke (). De plus, dans la mise en œuvre de Pete Montgomery, cela est encore plus frais. Il a une méthode Compose () qui vous permet de combiner n'importe quelle expression.

Nous prenons l'expression et à travers AndAlso nous nous connectons, fonctionne sans Expandable (). C'est cette implémentation qui est utilisée dans les opérations booléennes.
Spécifications et unités
Tout allait bien jusqu'à ce qu'il devienne clair que les agrégats existent dans la nature. Pour ceux qui ne sont pas familiers, je vais expliquer: si vous avez un modèle de domaine et que vous représentez toutes les entités qui sont liées les unes aux autres sous forme d'arbres, alors un arbre suspendu séparément est un agrégat. La commande ainsi que les articles de la commande seront appelés agrégats, et l'essence de la commande est la racine d'agrégation.

Si, en plus des produits, il existe encore des catégories avec une règle commerciale annoncée pour eux sous la forme d'une spécification, qu'il existe une certaine note qui devrait être supérieure à 50, comme l'ont dit les spécialistes du marketing et que nous voulons l'utiliser de cette façon, alors c'est bien.

Mais si nous voulons sortir les marchandises d'une bonne catégorie, c'est encore une fois mauvais, car nos types ne correspondent pas. Spécification pour la catégorie, mais des produits sont nécessaires.

Encore une fois, nous devons en quelque sorte résoudre le problème. La première option: remplacez Select () par SelectMany (). Je n'aime pas deux choses ici. Premièrement, je ne sais pas comment le support SelectMany () est implémenté dans tous les fournisseurs de requêtes populaires. Deuxièmement, si quelqu'un écrit un fournisseur de requêtes, la première chose qu'il fera sera d'écrire exception exception throw non implémentée et SelectMany (). Et le troisième point: les gens pensent que SelectMany () est soit une fonctionnalité, soit des jointures, généralement non associées à une requête SELECT.
La composition
Je voudrais utiliser Select (), pas SelectMany ().

Vers la même époque, j'ai lu sur la théorie des catégories, sur la composition fonctionnelle et j'ai pensé que s'il y a des spécifications du produit dans le bool ci-dessous, il y a une fonction qui peut aller du produit à la catégorie, il y a une spécification concernant la catégorie, puis en substituant la première fonctionner comme un argument de la seconde, nous obtenons ce dont nous avons besoin, une spécification concernant le produit. Absolument la même chose que les compositions fonctionnelles, mais pour les arbres d'expression.

Il serait alors possible d'écrire une telle méthode Where () qu'il est nécessaire de passer des produits aux catégories et d'appliquer la spécification à cette entité associée. Une telle syntaxe à mon goût subjectif semble assez compréhensible.
public static IQueryable<T> Where<T, TParam>(this IQueryable<T> queryable, Expression<Func<T, TParam>> prop, Expression<Func<TParam, bool>> where) { return queryable.Where(prop.Compose(where)); }
Avec la méthode Compose (), cela peut également être fait facilement. Nous prenons l'expression d'entrée des produits et la combinons avec les spécifications du produit et c'est tout.

Vous pouvez maintenant écrire un tel Where (). Cela fonctionnera si vous avez une machine de n'importe quelle longueur. La catégorie a une SuperCatégorie et un nombre illimité de propriétés supplémentaires qui peuvent être substituées.
«Puisque nous avons un outil de composition fonctionnelle, et puisque nous pouvons le compiler, et puisque nous pouvons l'assembler dynamiquement, cela signifie qu'il y a une odeur de méta-programmation!» Ai-je pensé.
Projections
Où pouvons-nous appliquer la méta-programmation pour que nous devions écrire moins de code.

La première option est la projection. Retirer une entité entière coûte souvent trop cher. Le plus souvent, nous le passons au premier plan, sérialisons JSON. Mais il n'a pas besoin de toute l'essence avec l'agrégat. Vous pouvez le faire avec LINQ aussi efficacement que possible en écrivant manuellement Select (). Pas difficile, mais ennuyeux.

Au lieu de cela, je suggère à tout le monde d'utiliser ProjectToType (). Au moins, deux bibliothèques peuvent le faire: Automapper et Mapster. Pour une raison quelconque, de nombreuses personnes savent qu'AutoMapper peut effectuer un mappage en mémoire, mais tout le monde ne sait pas qu'il possède des extensions interrogables, il a également une expression et il peut créer une expression SQL. Si vous écrivez toujours des requêtes manuelles et que vous utilisez LINQ, puisque vous n'avez pas de contraintes de performance très sérieuses, alors il n'y a aucun intérêt à le faire avec vos mains, c'est le travail de la machine, pas de la personne.
Filtrage
Si nous pouvons le faire avec des projections, pourquoi ne pas le faire pour le filtrage.

Voici aussi le code. Un filtre entre. De nombreuses applications d'entreprise ressemblent à ceci: un filtre est venu, ajoutez Where (), un autre filtre est venu, ajoutez Where (). Combien de filtres sont là, autant et répétez. Rien de compliqué, mais beaucoup de copier-coller.

Si nous, en tant qu'AutoMapper, le faisons, écrivons AutoFilter, Project and Filter pour qu'il fasse tout lui-même, ce serait du code cool-less.

Ce n'est rien de compliqué. Prenez Expression.Property, parcourez le DTO et essentiellement. Nous trouvons des propriétés communes appelées de manière identique. S'ils sont appelés de la même façon, cela ressemble à un filtre.
Ensuite, vous devez vérifier null, utiliser une constante pour obtenir la valeur de DTO, la remplacer dans l'expression et ajouter une conversion au cas où vous auriez Int et NullableInt ou un autre Nullable pour que les types correspondent. Et mettez, par exemple, Equals (), un filtre qui vérifie l'égalité.

Ensuite, récupérez lambda et passez en revue pour chaque propriété: s'il y en a beaucoup, collectez-les via «ET» ou «OU», selon la façon dont le filtre fonctionne pour vous.

La même chose peut être faite pour le tri, mais c'est un peu plus compliqué, car la méthode OrderBy () a deux génériques, vous devez donc les remplir avec vos mains, utilisez Reflections pour créer la méthode OrderBy () à partir de deux génériques, insérez le type d'entité que nous prenons, le type de triable Propriété. En général, vous pouvez également le faire, ce n'est pas difficile.
La question se pose: où mettre Où () - au niveau de l'entité, comme les spécifications ont été annoncées ou après la projection, et ça et là ça marchera.

C'est vrai à la fois, et donc, parce que les spécifications sont, par définition, des règles commerciales, et nous devons les chérir et les chérir et ne pas nous y tromper. Il s'agit d'une couche unidimensionnelle. Et les filtres sont plus sur l'interface utilisateur, ce qui signifie qu'ils filtrent par DTO. Par conséquent, vous pouvez mettre deux Where (). Il y a des questions plus probables quant à la façon dont le fournisseur de requêtes va gérer cela correctement, mais je pense que les solutions ORM écrivent de mauvais SQL de toute façon, et ce ne sera pas bien pire. Si c'est très important pour vous, alors cette histoire ne vous concerne pas du tout.

Comme on dit, il vaut mieux voir une fois qu'entendre cent fois.
Maintenant, le magasin propose trois produits: Snickers, Subaru Impreza et Mars. Magasin étrange. Essayons de trouver Snickers. Il y en a. Voyons ce que cent roubles. Aussi Snickers. Et pour 500? Zoomez, il n'y a rien. Et pour la Subaru Impreza 100500. Génial, il en va de même pour le tri.
Trier par ordre alphabétique et par prix. Le code y est écrit exactement comme il l'était. Ces filtres fonctionnent pour toutes les classes, peu importe. Si vous essayez de rechercher par nom, Subaru existe également. Et dans ma présentation était Equals (). Comment ça? Le fait est que le code ici et dans la présentation est un peu différent. J'ai commenté la ligne sur Equals () et ajouté de la magie de la rue spéciale. Si nous avons le type String, nous n'avons pas besoin de Equals (), mais appelons StartWith (), que j'ai également reçu. Par conséquent, un filtre différent est créé pour les lignes.

Cela signifie qu'ici, vous pouvez appuyer sur Ctrl + Maj + R, sélectionner la méthode et écrire non pas si, mais basculer, ou vous pouvez même implémenter le modèle de «stratégie», puis devenir fou. Vous pouvez réaliser tous les désirs concernant le fonctionnement des filtres. Tout dépend des types avec lesquels vous travaillez. Plus important encore, les filtres fonctionneront de la même manière.
Vous pouvez convenir que les filtres dans tous les éléments de l'interface utilisateur devraient fonctionner comme ceci: les chaînes sont recherchées d'une manière, l'argent est recherché d'une autre. Coordonnez tout cela, écrivez une fois, tout se fera correctement dans différentes interfaces, et aucun autre développeur ne le cassera, car ce code n'est pas au niveau de l'application, mais quelque part dans une bibliothèque externe ou dans votre noyau.
Validation
En plus du filtrage et de la projection, vous pouvez effectuer une validation. La bibliothèque JS
TComb.validation a eu cette idée. TComb est une abréviation de Type Combinators et il est basé sur un système de type et ainsi de suite. refinement', .
, JS, nill, undefined, .
. . , x >= 0 , Positive. . , , -.

. refinement, C#, IsValid(), Expression , . .
public class RefinementAttribute: ValidationAttribute { public IValidator<object> Refinement { get; } public RefinementAttribute(Type refinmentType) { Refinement = (IValidator<object>) Activator.CreateInstance(refinmentType); } public override bool IsValid(object value) => Refinement.Validate(value).IsValid(); }
DataAnnotations ASP.NET MVC, . RefinementAttribute(), . , RefinementAttribute , , .NET, .

. AdultRefinement, 18.

, . NoJS JS , . , C# , JS. JSX, ES6 JavaScript. ? Visitor, , JavaScript.

— , . regexp, StringBuilder, regexp. , JS — , bool , . , .
{ predicate: “x=> (x >= 18)”, errorMessage: “For adults only» }
, , , JS errorMessage «For adults only». . . , .
React, UserRefinment() Expression errorMessage, refinment number, eval, . , number, JS. , . , , false .

alert. onSubmit, alert , . .

Ok(ModelState.IsValid), User, JavaScript. Refinement.
using … namespace DemoApp.Core { public class User: HasNameBase { [Refinement(typeof(AdultRefinement))] public int Age { get; set; } } }
, . JavaScript. , - C#, , . NoJS, .
Test

., -, Moq. mock - — moq, fluent-. , .
moq — Expression, . , Castle.DynamicProxy. . .

, Core - WCF. , WebAPI. WebAPI, WCF WSDL . WebAPI swagger. swagger — , , API . WCF, WSDL, API, .
, , . moq GetResponse<>() ProductController, , , . , , Ctrl+Space , , , , dll . Intellisense, , .
, Moq, , , , API . , - , , , POST- GET-, , , Intellisense expression tree . , , Web-.
Reflection
-, Reflection.

, Reflection , . Expression. — CreateInstance. , Expression.New(), , .

. - . Activator, , . Constructor_Invoke, . — New compiled-. , , , , .

.

. - Fast Memember Fast Reflect, , . , , - . , , .

, , , . , , . , , - , , , , DSL, Little Languages -, .
, - , . , , , . DLR, , IronPython, IronRuby. Expression tree CLR. -, .
Résumé
, . , . , , - , .
— . 100 , . , , , . Expression Trees, - .
, , , , , .
, , . , .
, , Expression, Reflection, , , . , , API, , Expression . .
Expression.Compile() . , Expression' , Dictionary . - , , , , , Compile() . , .
— C#-, , , Where(), - implicit- . MSDN, . , , , , , , StackOverflow , - .
, , . , , ,
. , — .
22-23 DotNext 2018 Moscow . , ( ).