Eu entendo lambdas e o Func
e Action
delegados. Mas expressões me surpreendem.
Em que circunstâncias você usaria um Expression<Func<T>>
velho, e não um velho comum Func<T>
?
c#
delegates
lambda
expression-trees
Richard Nagle
fonte
fonte
Respostas:
Quando você deseja tratar expressões lambda como árvores de expressão e olhar dentro delas, em vez de executá-las. Por exemplo, LINQ to SQL obtém a expressão e a converte na instrução SQL equivalente e a envia ao servidor (em vez de executar o lambda).
Conceitualmente,
Expression<Func<T>>
é completamente diferente deFunc<T>
.Func<T>
denota umdelegate
que é praticamente um ponteiro para um método eExpression<Func<T>>
indica uma estrutura de dados em árvore para uma expressão lambda. Essa estrutura em árvore descreve o que uma expressão lambda faz em vez de fazer a coisa real. Ele basicamente contém dados sobre a composição de expressões, variáveis, chamadas de métodos, ... (por exemplo, ele contém informações como essa lambda é uma constante + algum parâmetro). Você pode usar esta descrição para convertê-la em um método real (comExpression.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 coisa de tempo de compilação.efetivamente será compilado para um método IL que não obtém nada e retorna 10.
será convertido em uma estrutura de dados que descreve uma expressão que não obtém parâmetros e retorna o valor 10:
Ampliar imagem
Enquanto os dois parecem iguais em tempo de compilação, o que o compilador gera é totalmente diferente .
fonte
Expression
contém as meta-informações sobre um determinado delegado.Expression<Func<...>>
vez de apenasFunc<...>
.(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
essa expressão é uma ExpressionTree, ramificações são criadas para a instrução If.Estou adicionando uma resposta para noobs, porque essas respostas pareceram exageradas, até que percebi como é simples. Às vezes, é sua expectativa de que é complicado que o torna incapaz de 'enrolar a cabeça'.
Não precisei entender a diferença até encontrar um 'bug' realmente irritante, tentando usar o LINQ-to-SQL genericamente:
Isso funcionou muito bem até eu começar a obter OutofMemoryExceptions em conjuntos de dados maiores. Definir pontos de interrupção dentro do lambda me fez perceber que ele estava iterando através de cada linha da minha tabela, um por um, procurando correspondências com minha condição de lambda. Isso me surpreendeu por um tempo, porque por que diabos está tratando minha tabela de dados como um IEnumerable gigante em vez de fazer LINQ-to-SQL como deveria? Ele também estava fazendo exatamente a mesma coisa no meu equivalente de LINQ para MongoDb.
A correção foi simplesmente transformar
Func<T, bool>
-seExpression<Func<T, bool>>
, então eu pesquisei por que ele precisa de um, emExpression
vez deFunc
acabar aqui.Uma expressão simplesmente transforma um delegado em um dado sobre si mesmo. Isso
a => a + 1
se torna algo como "No lado esquerdo, há umint 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 é basicamente tudo o que uma árvore de expressão realmente é - nada para se entender.Entendendo isso, fica claro por que o LINQ-to-SQL precisa de
Expression
eFunc
não é adequado.Func
não carrega consigo uma maneira de entrar em si mesmo, de ver o âmago da questão de como convertê-lo em uma consulta SQL / MongoDb / other. Você não pode ver se está fazendo adição, multiplicação ou 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. Isso permite que você traduza o delegado para o que quiser, como uma consulta SQL.Func
não funcionou porque meu DbContext estava cego para o conteúdo da expressão lambda. Por isso, não foi possível transformar a expressão lambda em SQL; no entanto, ele fez a melhor coisa seguinte e iterou essa condição através de cada linha da minha tabela.Edit: expondo minha última frase a pedido de John Peter:
IQueryable estende IEnumerable, então os métodos de IEnumerable, como
Where()
obter sobrecargas que aceitamExpression
. Quando você passa umExpression
para isso, mantém um IQueryable como resultado, mas quando passa umFunc
, você volta ao IEnumerable base e obtém um IEnumerable como resultado. Em outras palavras, sem perceber, você transformou seu conjunto de dados em uma lista para iteração, em vez de algo a ser consultado. É difícil notar uma diferença até que você realmente veja as assinaturas.fonte
Uma consideração extremamente importante na escolha do Expression vs Func é que os provedores IQueryable, como LINQ to Entities, podem 'digerir' o que você passa em uma Expressão, mas ignoram o que você passa em um Func. Eu tenho duas postagens de blog sobre o assunto:
Mais sobre Expressão vs Func com Entity Framework e Apaixonar-se pelo LINQ - Parte 7: Expressões e Funcs (a última seção)
fonte
Gostaria de adicionar algumas notas sobre as diferenças entre
Func<T>
eExpression<Func<T>>
:Func<T>
é apenas um MulticastDelegate à moda antiga;Expression<Func<T>>
é uma representação da expressão lambda na forma de árvore de expressão;Func<T>
;ExpressionVisitor
;Func<T>
;Expression<Func<T>>
.Há um artigo que descreve os detalhes com exemplos de código:
LINQ: Func <T> vs. Expressão <Func <T>> .
Espero que seja útil.
fonte
Há uma explicação mais filosófica sobre o assunto no livro de Krzysztof Cwalina ( Diretrizes de design de estrutura: convenções, expressões idiomáticas e padrões para bibliotecas .NET reutilizáveis );
Edite para versão sem imagem:
fonte
database.data.Where(i => i.Id > 0)
ser executado comoSELECT FROM [data] WHERE [id] > 0
. Se você acabou de passar em um Func, você colocou antolhos do seu driver e tudo o que pode fazer éSELECT *
e, em seguida, uma vez que é carregado todos os dados na memória, iterate através de cada e filtrar tudo com id> 0. Envolvendo o seuFunc
noExpression
capacita o driver para analisarFunc
e transformá-lo em uma consulta Sql / MongoDb / other.Expression
, mas quando estou de férias vai serFunc/Action
;)LINQ é o exemplo canônico (por exemplo, conversando com um banco de dados), mas, na verdade, sempre que você se preocupa mais em expressar o que fazer, em vez de realmente fazê-lo. Por exemplo, eu uso essa abordagem na pilha RPC do protobuf-net (para evitar a geração de código etc.) - então você chama um método com:
Isso desconstrói a árvore de expressão a ser resolvida
SomeMethod
(e o valor de cada argumento), executa a chamada RPC, atualiza qualquerref
/out
args e retorna o resultado da chamada remota. Isso só é possível através da árvore de expressão. Eu cubro isso mais aqui .Outro exemplo é quando você está construindo as árvores de expressão manualmente com o objetivo de compilar em um lambda, conforme feito pelo código genérico de operadores .
fonte
Você usaria uma expressão quando quiser tratar sua função como dados e não como código. Você pode fazer isso se desejar manipular o código (como dados). Na maioria das vezes, se você não vê necessidade de expressões, provavelmente não precisa usar uma.
fonte
O principal motivo é quando você não deseja executar o código diretamente, mas deseja inspecioná-lo. Isso pode ser por vários motivos:
fonte
Expression
pode ser tão impossível serializar quanto um delegado, pois qualquer expressão pode conter uma invocação de uma referência arbitrária de delegado / método. "Fácil" é relativo, é claro.Ainda não vejo respostas que mencionem desempenho. Passar
Func<>
s paraWhere()
ouCount()
é ruim. Muito ruim. Se você usar umFunc<>
, em seguida, ele chama oIEnumerable
material LINQ em vez deIQueryable
, o que significa que as tabelas inteiras são atraídas e , em seguida, filtrada.Expression<Func<>>
é significativamente mais rápido, especialmente se você estiver consultando um banco de dados que mora em outro servidor.fonte