method - what means=> in c#




Por que você usaria Expressão<Func<T>> em vez de Func<T>? (6)

Eu entendo lambdas e os delegados Func e Action . Mas as expressões me entopem. Em que circunstâncias você usaria uma Expression<Func<T>> vez de uma antiga Func<T> ?


A principal razão é quando você não quer executar o código diretamente, mas, em vez disso, deseja inspecioná-lo. Isso pode ser por vários motivos:

  • Mapeando o código para um ambiente diferente (ou seja, código C # para SQL no Entity Framework)
  • Substituindo partes do código em tempo de execução (programação dinâmica ou mesmo técnicas simples de DRY)
  • Validação de código (muito útil ao emular scripts ou ao fazer análises)
  • Serialização - as expressões podem ser serializadas de maneira fácil e segura, os representantes não podem
  • Segurança fortemente tipada em coisas que não são inerentemente fortemente tipadas, e explorando as verificações do compilador mesmo que você esteja fazendo chamadas dinâmicas em tempo de execução (a ASP.NET MVC 5 com o Razor é um bom exemplo)

Estou adicionando uma resposta para noobs porque essas respostas pareciam sobre minha cabeça, até que percebi como é simples. Às vezes é a sua expectativa de que é complicado que você seja incapaz de 'enrolar sua cabeça'.

Eu não precisei entender a diferença até que entrei em um 'bug' realmente irritante tentando usar o LINQ para SQL genericamente:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

Isso funcionou muito bem até que comecei a obter OutofMemoryExceptions em conjuntos de dados maiores. Definir pontos de interrupção dentro do lambda me fez perceber que estava interagindo em cada linha da minha mesa, uma a uma, procurando por correspondências com a condição de minha lambda. Isso me deixou perplexa por um tempo, porque por que diabos ele está tratando a minha tabela de dados como um IEnumerable gigante em vez de fazer LINQ para SQL como deveria? Ele também estava fazendo exatamente a mesma coisa na minha contraparte LINQ-to-MongoDb.

A correção foi simplesmente transformar Func<T, bool> em Expression<Func<T, bool>> , então eu pesquisei por que ele precisa de uma Expression vez de Func , terminando aqui.

Uma expressão simplesmente transforma um delegado em um dado sobre si mesmo. Então a => a + 1 se torna algo como "No lado esquerdo há um int a . No lado direito você adiciona 1 a ele." É isso aí. Você pode ir para casa agora. É obviamente mais estruturado do que isso, mas isso é essencialmente tudo que uma árvore de expressão realmente é - nada para quebrar sua cabeça.

Entendendo isso, fica claro por que o LINQ to SQL precisa de uma Expression , e um Func não é adequado. Func não traz consigo uma maneira de entrar em si mesmo, para ver o âmago da questão de como traduzi-lo em uma consulta SQL / MongoDb / other. Você não pode ver se está fazendo adição ou multiplicação na subtração. Tudo o que você pode fazer é executá-lo. Expression , por outro lado, permite que você olhe dentro do delegado e veja tudo o que ele deseja fazer, capacitando-o a traduzi-lo para o que você quiser, como uma consulta SQL. Func não funcionou porque meu DbContext era cego para o que estava realmente na expressão lambda para transformá-lo em SQL, então ele fez a próxima melhor coisa e iterou essa condicional através de cada linha na minha tabela.

Edit: expondo na minha última frase a pedido de John Peter:

O IQueryable estende IEnumerable, portanto, os métodos do IEnumerable, como Where() obtêm sobrecargas que aceitam Expression . Quando você passa uma Expression para isso, você mantém um IQueryable como resultado, mas quando você passa um Func , você está caindo na base IEnumerable e você obterá um IEnumerable como resultado. Em outras palavras, sem perceber, você transformou seu conjunto de dados em uma lista para ser iterado, em vez de algo para consultar. É difícil notar uma diferença até você realmente olhar sob o capô nas assinaturas.


Eu não vejo nenhuma resposta ainda que menciona o desempenho. Passar Func<> s para Where() ou Count() é ruim. Muito ruim. Se você usar um Func<> , ele chama o material IEnumerable LINQ em vez de IQueryable , o que significa que tabelas inteiras são extraídas e, em seguida, filtradas. Expression<Func<>> é significativamente mais rápida, especialmente se você estiver consultando um banco de dados que mora em outro servidor.


Há uma explicação mais filosófica sobre isso a partir do livro de Krzysztof Cwalina ( Framework Design Guidelines: Convenções, Expressões Idiomáticas e Padrões para Bibliotecas .NET Reutilizáveis );

Editar para versão sem imagem:

Na maioria das vezes você vai querer Func ou Action se tudo o que precisa acontecer é executar algum código. Você precisa de Expressão quando o código precisa ser analisado, serializado ou otimizado antes de ser executado. Expressão é para pensar em código, Func / Action é para executá-lo.


Quando você quiser tratar expressões lambda como árvores de expressão e olhar dentro delas em vez de executá-las. Por exemplo, o LINQ to SQL obtém a expressão e converte-a para a instrução SQL equivalente e a envia para o servidor (em vez de executar o lambda).

Conceitualmente, Expression<Func<T>> é completamente diferente de Func<T> . Func<T> denota um delegate que é praticamente um ponteiro para um método e Expression<Func<T>> denota uma estrutura de dados de árvore para uma expressão lambda. Essa estrutura de árvore descreve o que uma expressão lambda faz, em vez de fazer a coisa real. Basicamente, ele contém dados sobre a composição de expressões, variáveis, chamadas de método, ... (por exemplo, ele contém informações como esse lambda é alguma constante + algum parâmetro). Você pode usar essa descrição para convertê-lo em um método real (com Expression.Compile ) ou fazer outras coisas (como o exemplo LINQ to SQL) com ele. O ato de tratar lambdas como métodos anônimos e árvores de expressão é puramente uma questão de tempo de compilação.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

irá efetivamente compilar para um método IL que não recebe nada e retorna 10.

Expression<Func<int>> myExpression = () => 10;

será convertido em uma estrutura de dados que descreve uma expressão que não recebe parâmetros e retorna o valor 10:

imagem maior

Enquanto ambos parecem iguais em tempo de compilação, o que o compilador gera é totalmente diferente .








expression-trees