Para a maioria dos desenvolvedores, o uso da árvore de expressão é limitado às expressões lambda no LINQ. Frequentemente, não damos importância a como a tecnologia funciona "sob o capô".
Neste artigo, mostrarei técnicas avançadas para trabalhar com árvores de expressão: eliminando a duplicação de código no LINQ, geração de código, metaprogramação, transpilação e automação de testes.
Você aprenderá como usar a árvore de expressão diretamente, quais armadilhas a tecnologia preparou e como contorná-las.

Sob o recorte - transcrição de vídeo e texto do
meu relatório com o DotNext 2018 Piter.
Meu nome é Maxim Arshinov, sou co-fundador da empresa de terceirização do Hi-Tech Group. Estamos desenvolvendo software para negócios e hoje falarei sobre como a tecnologia da árvore de expressão foi usada no trabalho diário e como começou a nos ajudar.
Eu nunca quis estudar especificamente a estrutura interna das árvores de expressão, parecia que isso era algum tipo de tecnologia interna para o .NET Team for LINQ funcionar, e os programadores de aplicativos não precisavam conhecer sua API. Aconteceu que havia alguns problemas aplicados que precisavam ser resolvidos. Para que eu gostasse da solução, tive que subir no estômago.
Toda essa história se estende no tempo, houve projetos diferentes, casos diferentes. Algo saiu e eu terminei, mas me permitirei sacrificar a veracidade histórica em favor de uma apresentação mais artística, para que todos os exemplos estejam no mesmo modelo de assunto - uma loja on-line.

Imagine que todos nós estamos escrevendo uma loja online. Possui produtos e uma marca de seleção "À venda" no painel do administrador. Exibiremos apenas os produtos com essa marca de seleção marcada na parte pública.

Tomamos algum DbContext ou NHibernate, escrevemos Where (), produzimos IsForSale.
Está tudo bem, mas as regras de negócios não são as mesmas, de modo que as escrevemos de uma vez por todas. Eles evoluem com o tempo. Um gerente chega e diz que ainda devemos monitorar o saldo e exibir apenas mercadorias que possuem saldos na parte pública, sem esquecer a marca de seleção.

Nós adicionamos facilmente essa propriedade. Agora, nossas regras de negócios estão encapsuladas, podemos reutilizá-las.

Vamos tentar editar o LINQ. Está tudo bem aqui?
Não, isso não funcionará, porque IsAvailable não é mapeado para o banco de dados, este é o nosso código e o provedor de consultas não sabe como analisá-lo.

Podemos dizer a ele que nossa propriedade tem uma história dessas. Mas agora esse lambda é duplicado na expressão linq e na propriedade
Where(x => x.IsForSale && x.InStock > 0) IsAvailable => IsForSale && InStock > 0;
Portanto, na próxima vez que este lambda mudar, teremos que pressionar Ctrl + Shift + F no projeto. Naturalmente, todos nós não vamos encontrar - erros e tempo. Eu quero evitar isso.

Podemos ir deste lado e colocar outro ToList () na frente de Where (). Esta é uma péssima decisão, porque, se houver um milhão de produtos no banco de dados, todo mundo sobe para a RAM e filtra lá.

Se você tem três produtos em uma loja, a solução é boa, mas no comércio eletrônico geralmente há mais. Isso funcionou apenas porque, apesar da semelhança entre os lambdas, eles têm um tipo completamente diferente. No primeiro caso, este é um delegado da Func e, no segundo, uma árvore de expressão. Parece o mesmo, os tipos são diferentes, o bytecode é completamente diferente.

Para passar da expressão para um delegado, basta chamar o método Compile (). Esta API fornece .NET: existe expressão - compilada, recebida um delegado.
Mas como voltar? Existe alguma coisa no .NET para mudar de um delegado para árvores de expressão? Se você conhece o LISP, por exemplo, existe um mecanismo de citação que permite que o código seja interpretado como uma estrutura de dados, mas no .NET não é.
Expressar ou delegar?
Considerando que temos dois tipos de lambdas, podemos filosofar o que é primário: árvore de expressão ou delegados.
À primeira vista, a resposta é óbvia: como existe um maravilhoso método Compile (), a árvore de expressão é primária. E devemos receber o delegado compilando a expressão. Mas a compilação é um processo lento e, se começarmos a fazer isso em qualquer lugar, obteremos uma degradação no desempenho. Além disso, a receberemos em locais aleatórios, onde a expressão teve que ser compilada em um delegado, haverá uma redução no desempenho. Você pode encontrar esses locais, mas eles afetarão o tempo de resposta do servidor e aleatoriamente.

Portanto, eles precisam ser armazenados em cache de alguma forma. Se você ouviu uma palestra sobre estruturas de dados simultâneas, conhece o ConcurrentDictionary (ou apenas conhece). Omitirei detalhes sobre métodos de armazenamento em cache (com bloqueios, não bloqueios). Apenas o ConcurrentDictionary possui um método GetOrAdd () simples, e a implementação mais simples é inseri-lo no ConcurrentDictionary e armazená-lo em cache. A primeira vez que obtemos a compilação, mas tudo será rápido, porque o delegado já foi compilado.

Então você pode usar esse método de extensão, pode usar e refatorar nosso código com IsAvailable (), descrever a expressão, compilar as propriedades IsAvailable () e chamá-lo em relação ao objeto atual.
Há pelo menos dois pacotes que implementam isso:
Microsoft.Linq.Translations e
Signum Framework (estrutura de código aberto criada por uma empresa comercial). Lá e ali, há a mesma história com a compilação de delegados. API um pouco diferente, mas tudo como mostrei no slide anterior.
No entanto, essa não é a única abordagem, e você pode passar de delegados para expressões. Há muito tempo há
um artigo no Habre sobre o Descompilador Delegado, em que o autor afirma que toda a compilação é ruim, porque há muito tempo.
Em geral, os delegados estavam antes das expressões e você pode passar a eles a partir dos delegados. Para fazer isso, o autor usa o métodoBody.GetILAsByteArray (); do Reflection, que realmente retorna todo o código IL do método como uma matriz de bytes. Se você o aprofundar no Reflection, poderá obter uma representação de objeto desse caso, percorrê-lo com um loop e criar uma árvore de expressão. Assim, uma transição reversa também é possível, mas deve ser feita manualmente.

Para não percorrer todas as propriedades, o autor sugere suspender o atributo Computado para indicar que essa propriedade precisa ser incorporada. Antes da solicitação, subimos para IsAvailable (), extraímos seu código IL, convertemos para a árvore de expressão e substituímos a chamada IsAvailable () pelo que está escrito neste getter. Acontece que um manual embutido.

Para que isso funcione, antes de passar tudo para ToList (), chamamos o método especial Decompile (). Ele fornece um decorador para o original consultável e inlining. Somente depois disso, passamos tudo para o provedor de consultas e tudo está bem conosco.

O único problema com essa abordagem é que o Delegate Decompiler 0.23.0 não vai avançar, não há suporte para o Core e o próprio autor diz que esse é um alfa profundo, existem muitos inacabados, então você não pode usá-lo na produção. Embora retornaremos a este tópico.
Operações booleanas
Acontece que resolvemos o problema da duplicação de condições específicas.

Mas as condições geralmente precisam ser combinadas usando a lógica booleana. Tivemos IsForSale (), InStock ()> 0 e entre eles a condição "AND". Se houver qualquer outra condição, ou uma condição "OU" for necessária.

No caso de "And", você pode enganar e despejar todo o trabalho no provedor de consultas, ou seja, escrever muito Where () em uma linha, ele sabe como fazê-lo.

Se “OR” for necessário, isso não funcionará, porque WhereOr () não está no LINQ e o operador | | não está sobrecarregado com expressões.
Especificações
Se você está familiarizado com o livro DDD de Evans ou apenas conhece algo sobre o padrão Especificação, ou seja, um padrão de design projetado especificamente para isso. Existem várias regras de negócios e você deseja combinar operações na lógica booleana - implemente a especificação.

Uma especificação é esse termo, um padrão antigo do Java. E em Java, especialmente no antigo, não havia LINQ, por isso foi implementado apenas na forma do método isSatisfiedBy (), ou seja, apenas delegados, mas não houve discussão sobre expressões. Existe uma implementação na Internet chamada
LinqSpecs , no slide você a verá. Eu arquivei um pouco para mim, mas a ideia pertence à biblioteca.
Aqui, todos os operadores booleanos estão sobrecarregados, os operadores true e false são sobrecarregados, para que os dois operadores “&&” e “||” trabalhem, sem eles apenas um e comercial funcionará.

Em seguida, adicionamos instruções implícitas que fazem o compilador assumir que a especificação é expressões e delegados. Em qualquer lugar em que Expressão <> ou Func <> entrem na função, você pode passar a especificação. Como o operador implícito está sobrecarregado, o compilador analisará e substituirá as propriedades Expression ou IsSatisfiedBy.

IsSatisfiedBy () pode ser implementado armazenando em cache a expressão que veio. Em qualquer caso, verifica-se que estamos vindo de Expression, o delegado corresponde a ele, adicionamos suporte para operadores booleanos. Agora tudo isso pode ser arranjado. As regras de negócios podem ser colocadas em especificações estáticas, declaradas e 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 regra de negócios é gravada apenas uma vez, não será perdida em nenhum lugar, não será duplicada, poderá ser combinada. As pessoas que chegam ao projeto podem ver o que você tem, quais condições, entender o modelo de assunto.

Há um pequeno problema: a expressão não possui os métodos And (), Or () e Not (). Estes são métodos de extensão, eles devem ser implementados independentemente.

A primeira tentativa de implementação foi essa. Sobre a árvore de expressões, há bastante documentação na Internet e nem tudo é detalhado. Portanto, tentei apenas pegar Expression, pressione Ctrl + Space, vi OrElse (), li sobre isso. Passou duas Expressões para compilar e obter lambda. Isso não vai funcionar.

O fato é que essa expressão consiste em duas partes: parâmetro e corpo. O segundo também consiste em um parâmetro e um corpo. Em OrElse (), você precisa passar os corpos das expressões, ou seja, é inútil comparar lambdas com "AND" e "OR", isso não funcionará. Corrigimos, mas não funcionará novamente.
Mas se da última vez que houve uma NotSupportedException de que o lambda não era suportado, agora há uma história estranha sobre o parâmetro 1, parâmetro 2, "algo está errado, não vou funcionar".
C # 7.0 em poucas palavras
Então pensei que o método científico do puxão não funcionaria, preciso descobrir. Ele começou a pesquisar no Google e encontrou o site do livro de Albahari "
C # 7.0 in a Nutshell ".

Joseph Albahari, que também é o desenvolvedor da popular biblioteca LINQKit e LINQPad, apenas descreve esse problema. que você não pode simplesmente pegar e combinar Expression, e se você pegar a mágica Expression.Invoke (), ela funcionará.
Pergunta: o que é Expression.Invoke ()? Vá para o Google novamente. Ele cria uma InvocationExpression que aplica uma expressão delegada ou lambda à lista de argumentos.

Se eu li esse código para você agora que usamos Expression.Invoke (), passamos os parâmetros, então a mesma coisa está escrita em inglês. Não está ficando mais claro. Existe alguma Expression.Invoke () mágica que, por algum motivo, resolve esse problema com parâmetros. Devemos acreditar, não é necessário entender.

Ao mesmo tempo, se você tentar alimentar EFs com essa expressão combinada, ele cairá novamente e dirá que Expression.Invoke () não é suportado. A propósito, o EF Core começou a apoiar, mas o EF 6 não se sustenta. Mas Albahari apenas se oferece para escrever AsExpandable (), e tudo funciona.

E você pode substituir nas subconsultas Expressão em que precisamos de um representante. Para combiná-los, escrevemos Compile (), mas, ao mesmo tempo, se escrevermos AsExpandable (), como Albahari sugere, esse Compile () não acontecerá realmente, mas tudo será feito de forma mágica corretamente.

Não acreditei em uma palavra e entrei na fonte. O que é o método AsExpandable ()? Possui consulta e QueryOptimizer. Vamos deixar o segundo entre colchetes, pois é desinteressante, mas simplesmente cola Expressão: se houver 3 + 5, ele colocará 8.

É interessante que o método Expand () seja chamado posteriormente, depois o queryOptimizer e, em seguida, tudo seja passado ao provedor de consultas de alguma forma refeito após o método Expand ().

Nós o abrimos, é Visitor, por dentro vemos o Compile () não original, que compila outra coisa. Não direi exatamente o que, mesmo que isso tenha um certo significado, mas removemos uma compilação e a substituímos por outra. Ótimo, mas cheira a marketing de nível 80 porque o impacto no desempenho não está indo a lugar algum.
Em busca de uma alternativa
Eu pensei que isso não iria funcionar e comecei a procurar outra solução. E encontrou. Existe um tal Pete Montgomery que também
escreve sobre esse problema e afirma que Albahari fingiu.

Pete conversou com os desenvolvedores da EF e eles o ensinaram a combinar tudo sem Expression.Evoke (). A ideia é muito simples: a emboscada foi com os parâmetros. O fato é que, com a combinação Expressão, há um parâmetro da primeira expressão e um parâmetro da segunda. Eles não correspondem. Os corpos foram colados, mas os parâmetros permaneceram suspensos no ar. Eles precisam ser enfaixados da maneira certa.
Para fazer isso, você precisa compilar um dicionário observando os parâmetros das expressões, se o lambda não for de um parâmetro. Nós compomos um dicionário e re-ligamos todos os parâmetros do segundo aos parâmetros do primeiro, para que os parâmetros iniciais entrem em Expressão, conduzam por todo o corpo que colamos.

Um método tão simples permite que você se livre de todas as emboscadas com Expression.Invoke (). Além disso, na implementação de Pete Montgomery, isso é ainda mais interessante. Possui um método Compose () que permite combinar qualquer expressão.

Tomamos expressão e, através do AndAlso nos conectamos, funciona sem Expansível (). É essa implementação que é usada em operações booleanas.
Especificações e unidades
Tudo estava bem até ficar claro que os agregados existem na natureza. Para quem não conhece, explicarei: se você tem um modelo de domínio e representa todas as entidades relacionadas entre si na forma de árvores, uma árvore pendurada separadamente é um agregado. O pedido e os itens do pedido serão chamados de agregados, e a essência do pedido é a raiz da agregação.

Se, além dos produtos, ainda existem categorias com uma regra de negócios anunciada para eles na forma de uma especificação, existe uma certa classificação que deve ser superior a 50, como disseram os profissionais de marketing e queremos usá-la dessa maneira, então isso é bom.

Mas se queremos extrair os produtos de uma boa categoria, novamente é ruim, porque nossos tipos não coincidem. Especificação para a categoria, mas são necessários produtos.

Novamente, precisamos resolver o problema de alguma forma. A primeira opção: substitua Select () por SelectMany (). Eu não gosto de duas coisas aqui. Em primeiro lugar, não sei como o suporte ao SelectMany () é implementado em todos os provedores de consulta populares. Em segundo lugar, se alguém escreve um provedor de consultas, a primeira coisa que ele fará é escrever a exceção não implementada e SelectMany (). E o terceiro ponto: as pessoas pensam que SelectMany () é uma funcionalidade ou associação, geralmente não associada a uma consulta SELECT.
Composição:
Eu gostaria de usar Select (), não SelectMany ().

Na mesma época, li sobre teoria das categorias, sobre composição funcional e pensei que, se houver especificações do produto no bool abaixo, há alguma função que pode ir do produto para a categoria, há uma especificação referente à categoria, substituindo a primeira funcionar como argumento do segundo, obtemos o que precisamos, uma especificação referente ao produto. Absolutamente o mesmo que a composição funcional funciona, mas para árvores de expressão.

Então seria possível escrever um método Where () que seja necessário passar de produtos para categorias e aplicar a especificação a essa entidade relacionada. Essa sintaxe para o meu gosto subjetivo parece bastante compreensível.
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)); }
Com o método Compose (), isso também pode ser feito facilmente. Pegamos a Expressão de entrada dos produtos e a combinamos com as especificações do produto e é isso.

Agora você pode escrever como Where (). Isso funcionará se você tiver uma máquina de qualquer comprimento. Category possui uma SuperCategory e qualquer número de propriedades adicionais que podem ser substituídas.
“Como temos uma ferramenta para composição funcional, e como podemos compilá-la e como podemos montá-la dinamicamente, isso significa que há um cheiro de metaprogramação!”, Pensei.
Projeções
Onde podemos aplicar a metaprogramação para que tenhamos que escrever menos código.

A primeira opção é projeção. A retirada de uma entidade inteira geralmente é muito cara. Na maioria das vezes, passamos para a frente, serializamos JSON. Mas não precisa de toda a essência junto com o agregado. Você pode fazer isso com o LINQ da maneira mais eficiente possível, escrevendo esse Select () manualmente. Não é difícil, mas chato.

Em vez disso, sugiro que todos usem ProjectToType (). Pelo menos, existem duas bibliotecas que podem fazer isso: Automapper e Mapster. Por alguma razão, muitas pessoas sabem que o AutoMapper pode mapear na memória, mas nem todo mundo sabe que ele possui Extensões Queryable, também possui Expressão e pode criar uma expressão SQL. Se você ainda escrever consultas manuais e usar o LINQ, como não possui restrições de desempenho muito sérias, não há sentido em fazê-lo com as mãos, esse é o trabalho da máquina, não da pessoa.
Filtragem
Se podemos fazer isso com projeções, por que não fazer isso para filtrar.

Aqui está o código também. Um filtro entra. Muitos aplicativos de negócios são assim: um filtro veio, adicione Where (), outro filtro veio, adicione Where (). Quantos filtros existem, tantos e repita. Nada complicado, mas muita copiar e colar.

Se nós, como AutoMapper, fizermos, escrevermos AutoFilter, Projeto e Filtro para que ele faça tudo sozinho, seria um código legal - sem menos.

Isso não é nada complicado. Tome Expression.Property, percorra o DTO e, em essência. Encontramos propriedades comuns que são chamadas de forma idêntica. Se eles são chamados da mesma forma, parece um filtro.
Em seguida, é necessário verificar se há nulo, use uma constante para extrair o valor do DTO, substituí-lo na expressão e adicionar conversão caso você tenha Int e NullableInt ou outro Nullable para que os tipos correspondam. E coloque, por exemplo, Equals (), um filtro que verifica a igualdade.

Em seguida, colete o lambda e analise cada propriedade: se houver muitas delas, colete através de "AND" ou "OR", dependendo de como o filtro funciona para você.

O mesmo pode ser feito para a classificação, mas é um pouco mais complicado, porque o método OrderBy () possui dois genéricos, então você deve preenchê-los com as mãos, use o Reflections para criar o método OrderBy () a partir de dois genéricos, insira o tipo da entidade que estamos adotando, o tipo de classificação Propriedade. Em geral, você também pode fazer isso, não é difícil.
Surge a pergunta: onde colocar Onde () - no nível da entidade, conforme as especificações foram anunciadas ou após a projeção, e onde e onde funcionará.

É verdade tanto assim como porque as especificações são, por definição, regras de negócios, e devemos valorizá-las e valorizá-las e não cometer erros. Esta é uma camada unidimensional. E os filtros são mais sobre a interface do usuário, o que significa que são filtrados pelo DTO. Portanto, você pode colocar dois Where (). Existem perguntas mais prováveis sobre o quão bem o provedor de consulta lidará com isso muito bem, mas acredito que as soluções ORM gravam SQL incorreto de qualquer maneira e não será muito pior. Se isso é muito importante para você, essa história não é sobre você.

Como se costuma dizer, é melhor ver uma vez do que ouvir cem vezes.
Agora a loja possui três produtos: Snickers, Subaru Impreza e Mars. Loja estranha. Vamos tentar encontrar Snickers. Existe. Vamos ver o que cem rublos. Também Snickers. E por 500? Aumente o zoom, não há nada. E para o 100500 Subaru Impreza. Ótimo, o mesmo vale para a classificação.
Classifique alfabeticamente e por preço. O código lá está escrito exatamente como era. Esses filtros funcionam para qualquer classe, qualquer que seja. Se você tentar pesquisar pelo nome, o Subaru também existe. E na minha apresentação foi Equals (). Como assim? O fato é que o código aqui e na apresentação é um pouco diferente. Comentei a linha sobre Equals () e adicionei um pouco de magia de rua especial. Se tivermos o tipo String, não precisamos de Equals (), mas chamar StartWith (), que também recebi. Portanto, um filtro diferente é criado para as linhas.

Isso significa que aqui você pode pressionar Ctrl + Shift + R, selecionar o método e escrever não se, mas alternar, ou você pode até implementar o padrão "Estratégia" e ficar louco. Você pode realizar qualquer desejo sobre a operação dos filtros. Tudo depende dos tipos com os quais você trabalha. Mais importante ainda, os filtros funcionarão da mesma maneira.
Você pode concordar que os filtros em todos os elementos da interface do usuário devem funcionar assim: as cadeias são pesquisadas de uma maneira, o dinheiro é pesquisado de outra. Coordene tudo isso, escreva uma vez, tudo será feito corretamente em diferentes interfaces e nenhum outro desenvolvedor o quebrará, porque esse código não está no nível do aplicativo, mas em algum lugar da biblioteca externa ou do kernel.
Validação
Além de filtragem e projeção, você pode fazer a validação. A biblioteca JS
TComb.validation teve essa ideia. TComb é uma abreviação de Type Combinators e é baseada em um sistema de tipos e assim por diante.
refinamentos, melhorias.
Primeiro, as primitivas são declaradas correspondentes a todos os tipos JS e um tipo nill adicional correspondente a indefinido ou zero.
Então a diversão começa. Cada tipo pode ser aprimorado com um predicado. Se queremos números maiores que zero, declaramos o predicado x> = 0 e fazemos a validação com relação ao tipo Positivo. Portanto, a partir dos blocos de construção, você pode coletar qualquer uma de suas validações. Percebemos, provavelmente, também existem expressões lambda.
A chamada é aceita. Nós pegamos o mesmo refinamento, escrevemos em C #, escrevemos o método IsValid () e compilamos e executamos Expression também. Agora temos a oportunidade de realizar a validação. 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(); }
Integramos com o sistema DataAnnotations padrão no ASP.NET MVC, para que tudo funcione imediatamente. Declaramos RefinementAttribute (), passamos o tipo para o construtor. O fato é que RefinementAttribute é genérico, portanto, você deve usar um tipo como este, porque não é possível declarar um atributo genérico no .NET, infelizmente.
Portanto, marque a classe de usuário com refinanciamento. AdultRefinement, essa idade é maior que 18.
Para ser completamente bom, vamos fazer a validação no cliente e no servidor da mesma forma. Os apoiadores do NoJS sugerem escrever apoio e front em JS. Ok, vou escrever em C #, tudo bem e transponho para JS. Javascriptists podem escrever em seu JSX, ES6 e convertê-lo em JavaScript. Por que não podemos? Escrevemos Visitor, analisamos os operadores necessários e escrevemos JavaScript.
Um caso de validação frequente separado são expressões regulares, elas também precisam ser desmontadas. Se você possui regexp, use StringBuilder, crie regexp. Aqui eu usei dois pontos de exclamação, já que JS é uma linguagem de tipo dinâmico, essa expressão será convertida em bool sempre, para que tudo fique bem com o tipo. Vamos ver como fica. { predicate: “x=> (x >= 18)”, errorMessage: “For adults only» }
Aqui está o nosso refino, que vem do back-end, um predicado de linha, pois em JS não há lambdas e mensagem de erro "Somente para adultos". Vamos tentar preencher o formulário. Não passa. Olhamos como é feito.Isso é React, solicitamos do back-end do método UserRefinment () Expression e errorMessage, construímos um refinamento em relação ao número, usamos eval para obter o lambda. Se eu refazer isso e remover as restrições de tipo, substitua-o pelo número usual, a validação cairá no JS. Entre na unidade, envie. Não sei se é visível ou não, false foi deduzido aqui.
O código está alerta. Quando enviamos onSubmit, alerte o que veio do back-end. E o back-end é um código tão simples.
Simplesmente retornamos Ok (ModelState.IsValid), a classe User que obtemos do formulário em JavaScript. Aqui está este atributo de refinamento. using … namespace DemoApp.Core { public class User: HasNameBase { [Refinement(typeof(AdultRefinement))] public int Age { get; set; } } }
Ou seja, a validação também funciona no back-end, declarado neste lambda. E nós o traduzimos para JavaScript. Acontece que escrevemos expressões lambda em C # e o código é executado lá e ali. Nossa resposta é NoJS, também podemos fazer isso.Teste
Geralmente, os Timlids estão mais preocupados com o número de erros no código. Quem escreve testes de unidade conhece a biblioteca Moq. Deseja escrever mock ou declarar alguma classe - existe moq, possui sintaxe fluente. Você pode pintar como deseja que ele se comporte e aplicar seu pedido de teste.Essas lambdas no moq também são Expressão, não delegadas. Ele percorre as árvores de expressão, aplica sua lógica e depois alimenta o Castle.DynamicProxy. E ele cria as classes necessárias em tempo de execução. Mas nós podemos fazer isso também.
Um amigo meu perguntou recentemente se havia algo como o WCF em nosso Core. Eu respondi que existe uma WebAPI. Ele queria na WebAPI, como no WCF no WSDL, construir um proxy. Há apenas arrogância no WebAPI. Mas a arrogância é apenas texto, e um amigo não queria assistir sempre que a API muda e o que quebra. Quando havia o WCF, ele ativou o WSDL; se a especificação foi alterada na API, a compilação foi interrompida.Isso faz algum sentido, pois é relutante em pesquisar, e o compilador pode ajudar. Por analogia com moq, você pode declarar o método GetResponse <> () genérico com seu ProductController, e o lambda que entra nesse método é parametrizado pelo controlador. Ou seja, quando você começa a escrever lambda, pressione Ctrl + Space e veja todos os métodos que esse controlador possui, desde que haja uma biblioteca, uma dll com código. Existe o Intellisense, escreva tudo isso como se estivesse chamando o controlador.Além disso, como Moq, não vamos chamá-lo, mas simplesmente construa uma árvore de expressão, passe por ela, retire todas as informações de roteamento da configuração da API. E, em vez de fazer algo com o controlador, que não podemos executar, uma vez que devemos executá-lo no servidor, apenas fazemos a solicitação POST ou GET de que precisamos e, na direção oposta, desserializamos a recebida, porque de Intellisense e árvore de expressão que conhecemos sobre todos os tipos de retorno. Acontece que escrevemos o código sobre os controladores, mas, na verdade, fazemos solicitações na Web.Otimização de reflexãoTudo sobre a metaprogramação tem muito em comum com a reflexão.
Sabemos que a reflexão é lenta, eu gostaria de evitar isso. Aqui também existem bons casos de trabalho com o Expression. O primeiro é o ativador CreateInstance. Você nunca deve usá-lo, porque existe o Expression.New (), que você pode simplesmente acessar em um lambda, compilá-lo e obter os construtores.
Peguei esse slide emprestado de um maravilhoso palestrante e músico Vagif. Ele estava fazendo algum tipo de referência em um blog. Aqui está o ativador, este é o pico do comunismo, você vê o quanto ele está tentando fazer tudo. Constructor_Invoke, é cerca de metade do que é bom. E à esquerda está o lambda novo e compilado. Há um ligeiro aumento no desempenho devido ao fato de ser um delegado, não um construtor, mas a escolha é óbvia, é claro que isso é muito melhor.
O mesmo pode ser feito com getters ou setters.
Isso é feito de maneira muito simples. Se, por algum motivo, você não se sentir confortável com o Fast Memember, Mark Gravelli ou Fast Reflect, se não quiser arrastar essa dependência, poderá fazer o mesmo. A única dificuldade é que você precisa monitorar todas essas compilações, armazenar e aquecer o cache em algum lugar. Ou seja, se houver muito disso, no início é necessário compilar uma vez.
Como existe um construtor, getters e setters, existe apenas comportamento, métodos. Mas eles também podem ser compilados em delegados, e você terá apenas um grande zoológico de delegados que precisará gerenciar. Sabendo tudo sobre o que eu falei, pode ser que alguém pense que, se houver muitos delegados, muitas expressões, poderá haver espaço para o que é chamado DSL, Little Languages ou um padrão de intérprete, uma mônada livre.Essas são as mesmas coisas quando, para alguma tarefa, criamos um conjunto de comandos e, para ele, escrevemos nosso próprio intérprete que executa isso. Ou seja, dentro do aplicativo, escrevemos outro compilador ou intérprete que sabe como usar esses comandos. Isso é exatamente o que é feito no DLR, na parte que funciona com as linguagens IronPython e IronRuby. A árvore Expressão é usada para executar código dinâmico no CLR. O mesmo pode ser feito em aplicativos de negócios, mas até agora não percebemos essa necessidade e isso permanece fora dos colchetes.Sumário
Concluindo, quero falar sobre as conclusões a que chegamos após a implementação e o teste. Como eu disse, isso aconteceu em diferentes projetos. Tudo o que escrevi, não usamos em todos os lugares, mas em algum lugar, se necessário, algumas coisas foram usadas.A primeira vantagem é a capacidade de automatizar a rotina. Se você tem 100 mil moldes com filtragem, paginação e tudo isso. Mozart brincou dizendo que, usando dados, tempo suficiente e um copo de vinho tinto, você pode escrever valsas em qualquer quantidade. Aqui, com a ajuda do Expression Trees, uma pequena metaprogramação, você pode escrever formulários em qualquer quantidade.A quantidade de código é bastante reduzida, como alternativa à geração de código, se você não gosta, já que você recebe muito código, não pode escrevê-lo, deixa tudo em tempo de execução.Usando esse código para tarefas simples, reduzimos ainda mais os requisitos para os artistas, porque há muito pouco código imperativo e também não há espaço para erros. Depois de extrair uma grande quantidade de código em componentes reutilizáveis, removemos essa classe de erros.Por outro lado, aumentamos muito os requisitos para as qualificações do designer, porque surgem dúvidas sobre o trabalho com Expressão, Reflexão, sua otimização, sobre locais onde você pode dar um tiro no pé. Existem muitas nuances, portanto, uma pessoa não familiarizada com esta API não entenderá imediatamente por que o Expression simplesmente não combina. O designer deve ser mais descolado.Em alguns casos, através do Expression.Compile (), você pode detectar a degradação do desempenho. No exemplo de armazenamento em cache, eu tinha uma limitação de que Expressões são estáticas porque Dictionary é usado para armazenamento em cache. Se alguém não sabe como isso é organizado internamente, ele começa a fazê-lo sem pensar, declara as especificações não estáticas por dentro, o método de cache não funciona e receberemos chamadas para Compile () em locais aleatórios. Exatamente o que eu queria evitar.O menos desagradável é que o código deixa de se parecer com um código C #, torna-se menos idiomático, chamadas estáticas aparecem, métodos Where () adicionais estranhos, alguns operadores implícitos estão sobrecarregados. Isso não está na documentação do MSDN, nos exemplos. Se, por exemplo, uma pessoa com pouca experiência chegar até você, que não está acostumada a entrar no código-fonte em caso de algo, ela provavelmente estará em uma pequena prostração pela primeira vez, porque isso não se encaixa na imagem do mundo, não há exemplos no StackOverflow, mas com isso terá que trabalhar de alguma forma.Em geral, é sobre isso que eu queria falar hoje. Muito do que eu contei, com mais detalhes, está escrito em Habré. O código da biblioteca é publicado no github, mas possui uma falha fatal - a completa falta de documentação.22-23 DotNext 2018 Moscow . , ( ).