Árboles de expresión de desarrollo empresarial

Para la mayoría de los desarrolladores, el uso del árbol de expresión se limita a las expresiones lambda en LINQ. A menudo no le damos importancia a cómo funciona la tecnología "bajo el capó".

En este artículo, le mostraré técnicas avanzadas para trabajar con árboles de expresión: eliminar la duplicación de código en LINQ, generación de código, metaprogramación, transpilación, automatización de pruebas.

Aprenderá cómo usar el árbol de expresión directamente, qué dificultades ha preparado la tecnología y cómo sortearlas.



Debajo del corte: transcripción de video y texto de mi informe con DotNext 2018 Piter.


Mi nombre es Maxim Arshinov, soy cofundador de la empresa de outsourcing Hi-Tech Group. Estamos desarrollando software para empresas, y hoy hablaré sobre cómo se utilizó la tecnología del árbol de expresión en el trabajo diario y cómo comenzó a ayudarnos.

Nunca quise específicamente estudiar la estructura interna de los árboles de expresión, parecía que esta era una especie de tecnología interna para que funcione el equipo .NET para que LINQ funcionara, y los programadores de aplicaciones no necesitaban conocer su API. Resultó que había algunos problemas aplicados que debían resolverse. Para que me gustara la solución, tuve que subir al intestino.

Toda esta historia se extiende en el tiempo, hubo diferentes proyectos, diferentes casos. Algo salió y lo terminé, pero me permitiré sacrificar la veracidad histórica en favor de una presentación más artística, por lo que todos los ejemplos estarán en el mismo modelo de tema: una tienda en línea.



Imagine que todos estamos escribiendo una tienda en línea. Tiene productos y una marca de verificación "En venta" en el panel de administración. Solo mostraremos aquellos productos que tengan esta marca de verificación marcada en la parte pública.



Tomamos un poco de DbContext o NHibernate, escribimos Where (), sacamos IsForSale.

Todo está bien, pero las reglas de negocio no son las mismas, de modo que las escribimos de una vez por todas. Evolucionan con el tiempo. Llega un gerente y dice que aún debemos monitorear el saldo y mostrar solo los productos que tienen saldos en la parte pública, sin olvidar la marca de verificación.



Agregamos fácilmente dicha propiedad. Ahora que nuestras reglas comerciales están encapsuladas, podemos reutilizarlas.



Intentemos editar LINQ. ¿Está todo bien aquí?
No, esto no funcionará, porque IsAvailable no se asigna a la base de datos, este es nuestro código y el proveedor de consultas no sabe cómo analizarlo.



Podemos decirle que nuestra propiedad tiene una historia así. Pero ahora esta lambda está duplicada tanto en la expresión de linq como en la propiedad.

Where(x => x.IsForSale && x.InStock > 0) IsAvailable => IsForSale && InStock > 0; 

Entonces, la próxima vez que cambie este lambda, tendremos que hacer Ctrl + Shift + F en el proyecto. Naturalmente, no todos lo encontraremos: errores y tiempo. Quiero evitar esto



Podemos ir desde este lado y poner otro ToList () delante de Where (). Esta es una mala decisión, porque si hay un millón de productos en la base de datos, todo el mundo usa RAM y filtra allí.



Si tiene tres productos en una tienda, la solución es buena, pero en el comercio electrónico generalmente hay más. Esto funcionó solo porque, a pesar de la similitud de las lambdas entre sí, tienen un tipo completamente diferente. En el primer caso, este es un delegado de Func, y en el segundo, un árbol de expresión. Se ve igual, los tipos son diferentes, el código de bytes es completamente diferente.



Para pasar de la expresión a un delegado, solo necesita llamar al método Compile (). Esta API proporciona .NET: hay expresión - compilada, recibió un delegado.

¿Pero cómo volver? ¿Hay algo en .NET para pasar de un delegado a árboles de expresión? Si está familiarizado con LISP, por ejemplo, existe un mecanismo de citas que permite que el código se interprete como una estructura de datos, pero en .NET no lo es.

Expresar o delegados?


Teniendo en cuenta que tenemos dos tipos de lambdas, podemos filosofar lo que es primario: árbol de expresión o delegados.

 // so slo-oooooo-ow var delegateLambda = expressionLambda.Compile(); 

A primera vista, la respuesta es obvia: dado que hay un maravilloso método Compile (), el árbol de expresión es primario. Y deberíamos recibir al delegado compilando la expresión. Pero la compilación es un proceso lento, y si comenzamos a hacer esto en todas partes, se degrada el rendimiento. Además, lo recibiremos en lugares aleatorios, donde la expresión tuvo que compilarse en un delegado, habrá una reducción de rendimiento. Puede encontrar estos lugares, pero afectarán el tiempo de respuesta del servidor y al azar.



Por lo tanto, necesitan ser almacenados en caché de alguna manera. Si escuchó una charla sobre estructuras de datos concurrentes, entonces sabe acerca de ConcurrentDictionary (o simplemente lo sabe). Omitiré detalles sobre los métodos de almacenamiento en caché (con bloqueos, no bloqueos). Es solo que ConcurrentDictionary tiene un método GetOrAdd () simple, y la implementación más simple es introducirlo en ConcurrentDictionary y almacenarlo en caché. La primera vez que obtenemos la compilación, pero luego todo será rápido, porque el delegado ya ha sido compilado.



Luego puede usar dicho método de extensión, puede usar y refactorizar nuestro código con IsAvailable (), describir expresiones, compilar propiedades IsAvailable () y llamarlo en relación con el objeto actual.

Hay al menos dos paquetes que implementan esto: Microsoft.Linq.Translations y Signum Framework (marco de código abierto escrito por una empresa comercial). Tanto allí como hay casi la misma historia con la compilación de delegados. Una API un poco diferente, pero todo como lo mostré en la diapositiva anterior.

Sin embargo, este no es el único enfoque, y puede pasar de delegados a expresiones. Durante mucho tiempo ha habido un artículo sobre Habre sobre el descompilador delegado, donde el autor afirma que toda compilación es mala, porque durante mucho tiempo.

En general, los delegados estaban antes de las expresiones, y puede pasar a ellos desde los delegados. Para hacer esto, el autor utiliza el métodoBody.GetILAsByteArray (); de Reflection, que realmente devuelve todo el código IL del método como una matriz de bytes. Si lo coloca más en Reflection, puede obtener una representación de objeto de este caso, recorrerlo con un bucle y construir un árbol de expresión. Por lo tanto, también es posible una transición inversa, pero debe hacerse a mano.



Para no ejecutar todas las propiedades, el autor sugiere colgar el atributo Computado para indicar que esta propiedad debe estar en línea. Antes de la solicitud, subimos a IsAvailable (), extraemos su código IL, lo convertimos al árbol de expresión y reemplazamos la llamada IsAvailable () con lo que está escrito en este getter. Resulta una alineación manual de este tipo.



Para que esto funcione, antes de pasar todo a ToList (), llamamos al método especial Decompile (). Proporciona un decorador para el original consultable y en línea. Solo después de eso, pasamos todo al proveedor de consultas, y todo está bien para nosotros.



El único problema con este enfoque es que Delegate Decompiler 0.23.0 no avanzará, no hay soporte de Core, y el autor mismo dice que este es alfa profundo, hay muchos inacabados, por lo que no puede usarlo en producción. Aunque volveremos a este tema.

Operaciones booleanas


Resulta que hemos resuelto el problema de la duplicación de condiciones específicas.



Pero las condiciones a menudo deben combinarse utilizando la lógica booleana. Teníamos IsForSale (), InStock ()> 0, y entre ellos la condición "Y". Si hay alguna otra condición, o se requiere una condición "O".



En el caso de "Y", puede hacer trampa y volcar todo el trabajo en el proveedor de consultas, es decir, escribir mucho Where () en una fila, él sabe cómo hacerlo.



Si se requiere "OR", esto no funcionará porque WhereOr () no está en LINQ y el operador | | no está sobrecargado con expresiones.

Especificaciones


Si está familiarizado con el libro DDD de Evans o simplemente sabe algo sobre el patrón de Especificación, es decir, un patrón de diseño diseñado específicamente para esto. Existen varias reglas comerciales y desea combinar operaciones en lógica booleana: implemente la Especificación.



Una especificación es un término, un patrón antiguo de Java. Y en Java, especialmente en el anterior, no había LINQ, por lo que se implementó allí solo en la forma del método isSatisfiedBy (), es decir, solo delegados, pero no se habló de expresiones. Hay una implementación en Internet llamada LinqSpecs , en la diapositiva la verá. Lo archivé un poco para mí, pero la idea pertenece a la biblioteca.

Aquí, todos los operadores booleanos están sobrecargados, los operadores verdadero y falso están sobrecargados para que los dos operadores "&&" y "||" funcionen, sin ellos solo funcionará un ampersand.



A continuación, agregamos declaraciones implícitas que hacen que el compilador suponga que la especificación es tanto expresiones como delegados. En cualquier lugar donde Expression <> o Func <> entren en la función, puede pasar la especificación. Como el operador implícito está sobrecargado, el compilador analizará y sustituirá las propiedades Expression o IsSatisfiedBy.



IsSatisfiedBy () se puede implementar almacenando en caché la expresión que vino. En cualquier caso, resulta que venimos de Expression, el delegado corresponde a él, hemos agregado soporte para operadores booleanos. Ahora todo esto se puede arreglar. Las reglas comerciales se pueden poner en especificaciones estáticas, declaradas y combinadas.

 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); 



Cada regla de negocios se escribe solo una vez, no se perderá en ningún lado, no se duplicará, se pueden combinar. Las personas que vienen al proyecto pueden ver lo que tienes, qué condiciones, entender el modelo de tema.



Hay un pequeño problema: la expresión no tiene los métodos And (), Or () y Not (). Estos son métodos de extensión, deben implementarse independientemente.



El primer intento de implementación fue este. Sobre el árbol de expresión, hay bastante documentación en Internet, y no todo está detallado. Por lo tanto, intenté simplemente tomar Expression, presioné Ctrl + Espacio, vi OrElse (), leí sobre eso. Pasó dos expresiones para compilar y obtener lambda. Esto no va a funcionar.



El hecho es que esta expresión consta de dos partes: parámetro y cuerpo. El segundo también consta de un parámetro y un cuerpo. En OrElse (), debe pasar los cuerpos de las expresiones, es decir, es inútil comparar lambdas con "AND" y "OR", esto no funcionará. Lo arreglamos, pero no volverá a funcionar.

Pero si la última vez hubo una excepción NotSupportedException de que el lambda no era compatible, ahora hay una historia extraña sobre el parámetro 1, parámetro 2, "algo está mal, no funcionaré".

C # 7.0 en pocas palabras


Entonces pensé que el método científico no funcionaría, necesito resolverlo. Comenzó a buscar en Google y encontró el sitio del libro de Albahari " C # 7.0 en pocas palabras ".



Joseph Albahari, quien también es el desarrollador de la popular biblioteca LINQKit y LINQPad, simplemente describe este problema. que no puedes simplemente tomar y combinar Expression, y si tomas la expresión mágica.Invoke (), funcionará.

Pregunta: ¿qué es Expression.Invoke ()? Ve a Google nuevamente. Crea una InvocationExpression que aplica un delegado o una expresión lambda a la lista de argumentos.



Si le leo este código ahora que tomamos Expression.Invoke (), pasamos los parámetros, entonces lo mismo está escrito en inglés. No se está aclarando. Hay algo de Expression.Invoke mágico () que por alguna razón resuelve este problema con parámetros. Debemos creer, no es necesario entender.



Al mismo tiempo, si intenta alimentar EFs como Expresión combinada, caerá nuevamente y dirá que Expression.Invoke () no es compatible. Por cierto, EF Core comenzó a admitir, pero EF 6 no se mantiene. Pero Albahari solo ofrece escribir AsExpandable (), y todo funciona.



Y puede sustituir en subconsultas de expresión donde necesitamos un delegado. Para que coincidan, escribimos Compile (), pero al mismo tiempo, si escribimos AsExpandable (), como sugiere Albahari, este Compile () en realidad no sucederá, pero de alguna manera todo se hará mágicamente correctamente.



No creí una palabra y subí a la fuente. ¿Qué es el método AsExpandable ()? Tiene consulta y QueryOptimizer. Dejaremos el segundo entre paréntesis, ya que no es interesante, pero simplemente pega Expression: si hay 3 + 5, pondrá 8.



Es interesante que el método Expand () se llame más tarde, después de que el queryOptimizer, y luego todo se pase al proveedor de consultas de alguna manera rehecho después del método Expand ().



Lo abrimos, es Visitor, en el interior vemos el Compile no original (), que compila algo más. No le diré exactamente qué, aunque esto tenga un cierto significado, pero eliminamos una compilación y la reemplazamos por otra. Genial, pero huele a marketing de nivel 80 porque el impacto en el rendimiento no va a ninguna parte.

En busca de una alternativa


Pensé que esto no funcionaría y comencé a buscar otra solución. Y lo encontré. Existe Pete Montgomery, que también escribe sobre este problema y afirma que Albahari fingió.

Pete habló con los desarrolladores de EF, y le enseñaron a combinar todo sin Expression.Evoke (). La idea es muy simple: la emboscada fue con los parámetros. El hecho es que con la expresión de combinación hay un parámetro de la primera expresión y un parámetro de la segunda. No coinciden Los cuerpos estaban pegados, pero los parámetros permanecían suspendidos en el aire. Necesitan ser vendados de la manera correcta.

Para hacer esto, necesita compilar un diccionario mirando los parámetros de las expresiones, si la lambda no es de un parámetro. Redactamos un diccionario y volvemos a unir todos los parámetros del segundo a los parámetros del primero, de modo que los parámetros iniciales ingresen a Expresión, conduzcan por todo el cuerpo que unimos.

Un método tan simple le permite deshacerse de todas las emboscadas con Expression.Invoke (). Además, en la implementación de Pete Montgomery, esto se hace aún más genial. Tiene un método Compose () que le permite combinar cualquier expresión.



Tomamos expresión y a través de AndAlso también nos conectamos, funciona sin Expandable (). Es esta implementación la que se usa en las operaciones booleanas.

Especificaciones y unidades


Todo estuvo bien hasta que quedó claro que los agregados existen en la naturaleza. Para aquellos que no están familiarizados, explicaré: si tiene un modelo de dominio y representa todas las entidades que están relacionadas entre sí en forma de árboles, entonces un árbol que cuelga por separado es un agregado. El orden junto con los elementos del pedido se denominará agregado, y la esencia del pedido es la raíz de agregación.



Si, además de los productos, todavía hay categorías con una regla comercial anunciada para ellos en forma de especificación, que hay una cierta calificación que debería ser más de 50, como dijeron los especialistas en marketing y queremos usarla de esa manera, entonces esto es bueno.



Pero si queremos sacar los productos de una buena categoría, entonces nuevamente es malo, porque nuestros tipos no coinciden. Especificación para la categoría, pero se necesitan productos.



Nuevamente, necesitamos resolver el problema de alguna manera. La primera opción: reemplazar Select () con SelectMany (). No me gustan dos cosas aquí. En primer lugar, no sé cómo se implementa la compatibilidad con SelectMany () en todos los proveedores de consultas populares. En segundo lugar, si alguien escribe un proveedor de consultas, lo primero que hará es escribir excepción de lanzamiento no implementado y SelectMany (). Y el tercer punto: la gente piensa que SelectMany () es funcionalidad o se une, generalmente no asociada con una consulta SELECT.

Composición


Me gustaría usar Select (), no SelectMany ().



Casi al mismo tiempo, leí sobre la teoría de categorías, sobre la composición funcional y pensé que si hay especificaciones del producto en el bool a continuación, hay alguna función que puede ir del producto a la categoría, hay una especificación con respecto a la categoría, y luego sustituyo la primera Funcionamos como argumento del segundo, obtenemos lo que necesitamos, una especificación sobre el producto. Absolutamente lo mismo que funciona la composición funcional, pero para los árboles de expresión.



Entonces sería posible escribir un método Where () que sea necesario pasar de productos a categorías y aplicar la especificación a esta entidad relacionada. Tal sintaxis para mi gusto subjetivo parece bastante comprensible.

 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)); } 

Con el método Compose (), esto también se puede hacer fácilmente. Tomamos la expresión de entrada de los productos y la combinamos con las especificaciones del producto y listo.



Ahora puede escribir tales Where (). Esto funcionará si tiene una máquina de cualquier longitud. La categoría tiene una Supercategoría y cualquier número de propiedades adicionales que se pueden sustituir.

"Dado que tenemos una herramienta para la composición funcional, y dado que podemos compilarla, y dado que podemos ensamblarla dinámicamente, ¡significa que huele a metaprogramación!", Pensé.

Proyecciones


¿Dónde podemos aplicar la metaprogramación para que tengamos que escribir menos código?



La primera opción es la proyección. Sacar una entidad completa a menudo es demasiado costoso. Muy a menudo lo pasamos al frente, serializamos JSON. Pero no necesita toda la esencia junto con el agregado. Puede hacer esto con LINQ de la manera más eficiente posible escribiendo tal Select () manualmente. No es difícil, pero aburrido.



En cambio, sugiero que todos usen ProjectToType (). Al menos hay dos bibliotecas que pueden hacer esto: Automapper y Mapster. Por alguna razón, muchas personas saben que AutoMapper puede hacer mapeos en la memoria, pero no todos saben que tiene Extensiones consultables, también tiene Expresión y puede construir una expresión SQL. Si todavía escribe consultas manuales y usa LINQ, ya que no tiene restricciones de rendimiento muy graves, entonces no tiene sentido hacerlo con las manos, este es el trabajo de la máquina, no de la persona.

Filtrado


Si podemos hacer esto con proyecciones, ¿por qué no hacerlo para filtrar?



Aquí está el código también. Entra un filtro. Muchas aplicaciones comerciales se ven así: vino un filtro, agregue Where (), vino otro filtro, agregue Where (). Cuántos filtros hay, tantos y repite. Nada complicado, pero mucho copiar y pegar.



Si nosotros, como AutoMapper, lo hacemos, escribimos AutoFilter, Project y Filter para que él haga todo por sí mismo, sería un código genial.



Esto no es nada complicado. Tome Expression.Property, camine por el DTO y, en esencia. Encontramos propiedades comunes que se llaman de forma idéntica. Si se les llama igual, se ve como un filtro.

A continuación, debe buscar nulo, usar una constante para extraer el valor del DTO, sustituirlo en la expresión y agregar conversión en caso de que tenga Int y NullableInt u otro Nullable para que los tipos coincidan. Y ponga, por ejemplo, Equals (), un filtro que verifica la igualdad.



Luego recolecte lambda y vaya a cada propiedad: si hay muchas, recójalas a través de “Y” o “O”, dependiendo de cómo funcione el filtro para usted.



Se puede hacer lo mismo para ordenar, pero es un poco más complicado, ya que el método OrderBy () tiene dos genéricos, por lo que debe completarlos con las manos, usar Reflections para crear el método OrderBy () a partir de dos genéricos, insertar el tipo de entidad que estamos tomando, el tipo de ordenable Propiedad. En general, también puede hacer esto, no es difícil.

Surge la pregunta: dónde colocar Where (): a nivel de entidad, como se anunciaron las especificaciones o después de la proyección, y allí y allí funcionará.



Es cierto ambos, y así, porque las especificaciones son, por definición, reglas comerciales, y debemos apreciarlas y apreciarlas y no equivocarnos con ellas. Esta es una capa unidimensional. Y los filtros tienen más que ver con la interfaz de usuario, lo que significa que filtran por DTO. Por lo tanto, puede poner dos Where (). Hay preguntas más probables sobre qué tan bien el proveedor de consultas manejará esto bien, pero creo que las soluciones ORM escriben SQL incorrecto de todos modos, y no será mucho peor. Si esto es muy importante para usted, entonces esta historia no trata sobre usted en absoluto.



Como dicen, es mejor ver una vez que escuchar cien veces.
Ahora la tienda tiene tres productos: Snickers, Subaru Impreza y Mars. Extraña tienda. Intentemos encontrar Snickers. Hay Veamos qué cien rublos. También Snickers. ¿Y por 500? Acercar, no hay nada. Y para el 100500 Subaru Impreza. Genial, lo mismo ocurre con la clasificación.

Ordenar alfabéticamente y por precio. El código allí está escrito exactamente tanto como estaba. Estos filtros funcionan para cualquier clase, lo que sea. Si intenta buscar por nombre, entonces Subaru también existe. Y en mi presentación fue Igual (). ¿Cómo es eso? El hecho es que el código aquí y en la presentación son un poco diferentes. Comenté la línea sobre Equals () y agregué algo de magia callejera especial. Si tenemos el tipo String, entonces no necesitamos Equals (), sino que llamamos a StartWith (), que también recibí. Por lo tanto, se crea un filtro diferente para las filas.



Esto significa que aquí puede presionar Ctrl + Shift + R, seleccionar el método y escribir no si, sino cambiar, o incluso puede implementar el patrón de "Estrategia" y luego volverse loco. Puede realizar cualquier deseo sobre el funcionamiento de los filtros. Todo depende de los tipos con los que trabajas. Lo más importante, los filtros funcionarán igual.

Puede aceptar que los filtros en todos los elementos de la interfaz de usuario deberían funcionar así: las cadenas se buscan de una manera, el dinero se busca en otra. Coordine todo esto, escriba una vez, todo se hará correctamente en diferentes interfaces, y ningún otro desarrollador lo romperá, porque este código no está en el nivel de la aplicación, sino en algún lugar de una biblioteca externa o en su núcleo.

Validación


Además del filtrado y la proyección, puede hacer la validación. La biblioteca JS TComb.validation surgió con esta idea. TComb es una abreviatura de Type Combinators y se basa en un sistema de tipos, etc. refinamientos, mejoras.

 // null and undefined validate('a', t.Nil).isValid(); // => false validate(null, t.Nil).isValid(); // => true validate(undefined, t.Nil).isValid(); // => true // strings validate(1, t.String).isValid(); // => false validate('a', t.String).isValid(); // => true // numbers validate('a', t.Number).isValid(); // => false validate(1, t.Number).isValid(); // => true 

Primero, las primitivas se declaran correspondientes a todos los tipos JS, y un tipo de nulo adicional correspondiente a indefinido o cero.

 // a predicate is a function with signature: (x) -> boolean var predicate = function (x) { return x >= 0; }; // a positive number var Positive = t.refinement(t.Number, predicate); validate(-1, Positive).isValid(); // => false validate(1, Positive).isValid(); // => true 

Entonces comienza la diversión. Cada tipo se puede mejorar con un predicado. Si queremos números mayores que cero, declaramos el predicado x> = 0 y hacemos la validación con respecto al tipo Positivo. Entonces, desde los bloques de construcción, puede recopilar cualquiera de sus validaciones. Notamos, probablemente, también hay expresiones lambda.



La llamada es aceptada. Tomamos el mismo refinamiento, lo escribimos en C #, escribimos el método IsValid (), y también compilamos y ejecutamos Expression. Ahora tenemos la oportunidad de realizar la validación.

 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.

 usingnamespace DemoApp.Core { public class User: HasNameBase { [Refinement(typeof(AdultRefinement))] public int Age { get; set; } } } 

, . JavaScript. , - C#, , . NoJS, .

Prueba




., -, Moq. mock - — moq, fluent-. , .

moq — Expression, . , Castle.DynamicProxy. . .



Un amigo mío recientemente preguntó si había algo como WCF en nuestro Core. Le respondí que hay un WebAPI. Quería en WebAPI, como en WCF en WSDL, construir un proxy. Solo hay arrogancia en WebAPI. Pero swagger es solo texto, y un amigo no quería ver cada vez que cambia la API y qué se rompe. Cuando hubo WCF, habilitó WSDL, si la especificación ha cambiado en la API, la compilación se rompió.

, , . 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 -, .

Todas estas son las mismas cosas cuando para alguna tarea se nos ocurre un conjunto de comandos y para él escribimos nuestro propio intérprete que realiza esto. Es decir, dentro de la aplicación escribimos otro compilador o intérprete que sabe cómo usar estos comandos. Esto es exactamente lo que se hace en DLR, en la parte que funciona con IronPython, los idiomas IronRuby. El árbol de expresión allí se usa para ejecutar código dinámico en el CLR. Lo mismo puede hacerse en aplicaciones comerciales, pero hasta ahora no hemos notado tal necesidad y esto permanece fuera de los corchetes.

Resumen


, . , . , , - , .

— . 100 , . , , , . Expression Trees, - .

, , , , , .

, , . , .

, , Expression, Reflection, , , . , , API, , Expression . .

Expression.Compile() . , Expression' , Dictionary . - , , , , , Compile() . , .

— C#-, , , Where(), - implicit- . MSDN, . , , , , , , StackOverflow , - .

, , . , , , . , — .
22-23 DotNext 2018 Moscow . , ( ).

Source: https://habr.com/ru/post/es423891/


All Articles