Expressões em C # Lambda: Por que devo usá-las?

309

Eu li rapidamente sobre o Microsoft Lambda Expression documentação .

Esse tipo de exemplo me ajudou a entender melhor:

delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

Ainda assim, não entendo por que é uma inovação. É apenas um método que morre quando a "variável de método" termina, certo? Por que devo usar isso em vez de um método real?

Patrick Desjardins
fonte
3
Para aqueles de vocês que vêm a esta página e não sabem o que delegateé um C #, sugiro que você leia isso antes de ler o restante desta página: stackoverflow.com/questions/2082615/…
Kolob Canyon

Respostas:

281

Expressões lambda são uma sintaxe mais simples para delegados anônimos e podem ser usadas em qualquer lugar em que um delegado anônimo possa ser usado. No entanto, o oposto não é verdadeiro; As expressões lambda podem ser convertidas em árvores de expressão, o que permite muita mágica, como LINQ to SQL.

A seguir, é apresentado um exemplo de expressão de LINQ to Objects usando delegados anônimos e expressões lambda para mostrar como são mais fáceis para os olhos:

// anonymous delegate
var evens = Enumerable
                .Range(1, 100)
                .Where(delegate(int x) { return (x % 2) == 0; })
                .ToList();

// lambda expression
var evens = Enumerable
                .Range(1, 100)
                .Where(x => (x % 2) == 0)
                .ToList();

Expressões lambda e delegados anônimos têm uma vantagem em escrever uma função separada: eles implementam fechamentos que permitem passar o estado local para a função sem adicionar parâmetros à função ou criar objetos de uso único.

As árvores de expressão são um novo recurso muito poderoso do C # 3.0 que permite que uma API observe a estrutura de uma expressão em vez de apenas obter uma referência a um método que pode ser executado. Uma API apenas precisa transformar um parâmetro delegado em um Expression<T>parâmetro e o compilador gerará uma árvore de expressão de um lambda em vez de um delegado anônimo:

void Example(Predicate<int> aDelegate);

chamado como:

Example(x => x > 5);

torna-se:

void Example(Expression<Predicate<int>> expressionTree);

Este último receberá uma representação da árvore de sintaxe abstrata que descreve a expressão x > 5. O LINQ to SQL depende desse comportamento para poder transformar expressões C # nas expressões SQL desejadas para filtragem / pedido / etc. no lado do servidor.

Neil Williams
fonte
1
Sem fechamentos, você pode usar métodos estáticos como retornos de chamada, mas ainda precisa defini-los em alguma classe, aumentando quase certamente o escopo desse método além do uso pretendido.
DK.
10
FWIW, você pode ter encerramentos com um delegado anônimo, para não precisar estritamente de lambdas para isso. As lambdas são eminentemente mais legíveis do que os delegados anônimos, sem os quais o uso do Linq faria seus olhos sangrarem.
29119 Benjol
138

Funções e expressões anônimas são úteis para métodos pontuais que não se beneficiam do trabalho extra necessário para criar um método completo.

Considere este exemplo:

 string person = people.Find(person => person.Contains("Joe"));

versus

 public string FindPerson(string nameContains, List<string> persons)
 {
     foreach (string person in persons)
         if (person.Contains(nameContains))
             return person;
     return null;
 }

Estes são funcionalmente equivalentes.

Joseph Daigle
fonte
8
Como teria sido o método Find () definido para lidar com essa expressão lambda?
Patrick Desjardins
3
Predicado <T> é o que o método Find está esperando.
Darren Kopp
1
Como minha expressão lambda corresponde ao contrato do Predicate <T>, o método Find () a aceita.
Joseph Daigle
você quis dizer "string person = people.Find (people => persons.Contains (" Joe "));"
Gern Blanston 02/04/09
5
@FKCoder, não, ele não diz, embora possa ter ficado mais claro se ele dissesse "string person = people.Find (p => p.Contains (" Joe "));"
29119 Benjol
84

Eu os achei úteis em uma situação em que desejei declarar um manipulador para algum evento de controle, usando outro controle. Para fazer isso normalmente, você teria que armazenar as referências dos controles nos campos da classe para poder usá-las em um método diferente do que elas foram criadas.

private ComboBox combo;
private Label label;

public CreateControls()
{
    combo = new ComboBox();
    label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += new EventHandler(combo_SelectedIndexChanged);
}

void combo_SelectedIndexChanged(object sender, EventArgs e)
{
    label.Text = combo.SelectedValue;
}

Graças às expressões lambda, você pode usá-lo assim:

public CreateControls()
{
    ComboBox combo = new ComboBox();
    Label label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += (s, e) => {label.Text = combo.SelectedValue;};
}

Muito facil.

agnieszka
fonte
No primeiro exemplo, por que não transmitir remetente e obter o valor?
Andrew
@ Andrew: Neste exemplo simples, não é necessário usar o remetente, porque há apenas um componente em questão e o uso do campo salva diretamente um elenco, o que melhora a clareza. Em um cenário do mundo real, eu pessoalmente prefiro usar o remetente também. Normalmente, eu uso um manipulador de eventos para vários eventos, se possível e, portanto, tenho que identificar o remetente real.
precisa saber é o seguinte
35

A lambda limpou a sintaxe de delegado anônimo do C # 2.0 ... por exemplo

Strings.Find(s => s == "hello");

Foi feito em C # 2.0 assim:

Strings.Find(delegate(String s) { return s == "hello"; });

Funcionalmente, eles fazem exatamente a mesma coisa, é apenas uma sintaxe muito mais concisa.

FlySwat
fonte
3
Eles não são exatamente a mesma coisa - como @Neil Williams salienta, você pode extrair o AST de uma lambda usando árvores de expressão, enquanto métodos anônimos não podem ser usados ​​da mesma maneira.
Ljs
Essa é uma das muitas outras vantagens do lambda. Ajuda a entender melhor o código que os métodos anônimos. certamente não é a intenção de criar lambdas, mas estes são cenários em que podem ser usados ​​com mais frequência.
Guruji
29

Essa é apenas uma maneira de usar uma expressão lambda. Você pode usar uma expressão lambda em qualquer lugar em que possa usar um delegado. Isso permite que você faça coisas assim:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

strings.Find(s => s == "hello");

Este código pesquisará na lista uma entrada que corresponda à palavra "olá". A outra maneira de fazer isso é realmente passar um delegado para o método Find, assim:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

private static bool FindHello(String s)
{
    return s == "hello";
}

strings.Find(FindHello);

EDIT :

No C # 2.0, isso pode ser feito usando a sintaxe de delegado anônimo:

  strings.Find(delegate(String s) { return s == "hello"; });

O Lambda limpou significativamente essa sintaxe.

Scott Dorman
fonte
2
@ Jonathan Holland: Obrigado pela edição e adição da sintaxe do delegado anônimo. Ele completa bem o exemplo.
Scott Dorman
o que é delegado anônimo // pesaroso eu sou novo para c #?
Hackerman
1
@HackerMan, pense em um delegado anônimo como uma função que não tem um "nome". Você ainda está definindo uma função, que pode ter entrada e saída, mas como é um nome, você não pode se referir a ela diretamente. No código mostrado acima, você está definindo um método (que recebe stringae retorna a bool) como um parâmetro para o Findpróprio método.
Scott Dorman
22

A Microsoft nos deu uma maneira mais limpa e conveniente de criar delegados anônimos chamados expressões Lambda. No entanto, não há muita atenção sendo prestada à parte de expressões desta declaração. A Microsoft lançou um espaço para nome inteiro, System.Linq.Expressions , que contém classes para criar árvores de expressão com base em expressões lambda. As árvores de expressão são compostas de objetos que representam a lógica. Por exemplo, x = y + z é uma expressão que pode fazer parte de uma árvore de expressão em .Net. Considere o seguinte exemplo (simples):

using System;
using System.Linq;
using System.Linq.Expressions;


namespace ExpressionTreeThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int>> expr = (x) => x + 1; //this is not a delegate, but an object
            var del = expr.Compile(); //compiles the object to a CLR delegate, at runtime
            Console.WriteLine(del(5)); //we are just invoking a delegate at this point
            Console.ReadKey();
        }
    }
}

Este exemplo é trivial. E tenho certeza que você está pensando: "Isso é inútil, pois eu poderia ter criado diretamente o delegado em vez de criar uma expressão e compilá-la em tempo de execução". E você estaria certo. Mas isso fornece a base para as árvores de expressão. Há várias expressões disponíveis nos namespaces Expressions e você pode criar suas próprias. Eu acho que você pode ver que isso pode ser útil quando você não sabe exatamente qual deve ser o algoritmo no design ou no tempo de compilação. Eu vi um exemplo em algum lugar para usar isso para escrever uma calculadora científica. Você também pode usá-lo para sistemas bayesianos ou para programação genética(AI). Algumas vezes na minha carreira, tive que escrever uma funcionalidade semelhante ao Excel que permitia aos usuários inserir expressões simples (adição, subtração, etc.) para operar com os dados disponíveis. Na pré-.Net 3.5, tive que recorrer a alguma linguagem de script externa ao C #, ou tive que usar a funcionalidade de emissão de código na reflexão para criar código .Net rapidamente. Agora eu usaria árvores de expressão.

Jason Jackson
fonte
12

Isso evita que os métodos que são usados ​​apenas uma vez em um local específico sejam definidos longe do local em que são usados. Os bons usos são como comparadores de algoritmos genéricos, como classificação, em que você pode definir uma função de classificação personalizada na qual você está chamando a classificação, em vez de forçar a procurar em outro lugar para ver em que está classificando.

E não é realmente uma inovação. O LISP possui funções lambda há cerca de 30 anos ou mais.

workmad3
fonte
6

Você também pode encontrar o uso de expressões lambda ao escrever códigos genéricos para atuar em seus métodos.

Por exemplo: Função genérica para calcular o tempo gasto por uma chamada de método. (ou seja Action, aqui)

public static long Measure(Action action)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

E você pode chamar o método acima usando a expressão lambda da seguinte maneira,

var timeTaken = Measure(() => yourMethod(param));

A expressão permite que você obtenha valor de retorno do seu método e também dos parâmetros

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));
Gunasekaran
fonte
5

A expressão lambda é uma maneira concisa de representar um método anônimo. Os métodos anônimos e as expressões Lambda permitem definir a implementação do método em linha, no entanto, um método anônimo exige explicitamente que você defina os tipos de parâmetros e o tipo de retorno para um método. A expressão lambda usa o recurso de inferência de tipo do C # 3.0, que permite ao compilador inferir o tipo da variável com base no contexto. É muito conveniente, pois economiza bastante digitação!

Vijesh VP
fonte
5

Uma expressão lambda é como um método anônimo escrito no lugar de uma instância delegada.

delegate int MyDelagate (int i);
MyDelagate delSquareFunction = x => x * x;

Considere a expressão lambda x => x * x;

O valor do parâmetro de entrada é x (no lado esquerdo de =>)

A lógica da função é x * x (no lado direito de =>)

O código de uma expressão lambda pode ser um bloco de instruções em vez de uma expressão.

x => {return x * x;};

Exemplo

Nota: Funcé um delegado genérico predefinido.

    Console.WriteLine(MyMethod(x => "Hi " + x));

    public static string MyMethod(Func<string, string> strategy)
    {
        return strategy("Lijo").ToString();
    }

Referências

  1. Como um delegado e uma interface podem ser usados ​​de forma intercambiável?
LCJ
fonte
4

Na maioria das vezes, você está usando apenas a funcionalidade em um só lugar, portanto, criar um método desorganiza a classe.

Darren Kopp
fonte
3

É uma maneira de executar pequenas operações e colocá-las muito perto de onde são usadas (não muito diferente de declarar uma variável próxima ao seu ponto de uso). Isso deve tornar seu código mais legível. Ao anonimizar a expressão, você também torna muito mais difícil alguém quebrar o código do cliente se a função for usada em outro lugar e modificada para "aprimorá-la".

Da mesma forma, por que você precisa usar o foreach? Você pode fazer tudo no foreach com um loop for simples ou apenas usando IEnumerable diretamente. Resposta: você não precisa , mas torna seu código mais legível.

plinto
fonte
0

A inovação está no tipo segurança e transparência. Embora você não declare tipos de expressões lambda, elas são inferidas e podem ser usadas por pesquisa de código, análise estática, ferramentas de refatoração e reflexão em tempo de execução.

Por exemplo, antes de você ter usado o SQL e receber um ataque de injeção de SQL, porque um hacker passou uma string em que normalmente era esperado um número. Agora você usaria uma expressão lambda LINQ, que é protegida disso.

A criação de uma API LINQ em delegados puros não é possível, pois requer a combinação de árvores de expressão antes de avaliá-las.

Em 2016, a maioria dos idiomas populares possui suporte à expressão lambda , e o C # foi um dos pioneiros nessa evolução entre os principais idiomas imperativos.

battlmonstr
fonte
0

Esta é talvez a melhor explicação sobre por que usar expressões lambda -> https://youtu.be/j9nj5dTo54Q

Em resumo, é para melhorar a legibilidade do código, reduzir as chances de erros reutilizando em vez de replicar o código e aproveitar a otimização que ocorre nos bastidores.

coffeeeee
fonte
0

O maior benefício das expressões lambda e funções anônimas é o fato de permitir que o cliente (programador) de uma biblioteca / estrutura injete funcionalidade por meio de código na biblioteca / estrutura fornecida (como o LINQ, o ASP.NET Core e muitos outros) de uma maneira que os métodos regulares não podem. No entanto, sua força não é óbvia para um único programador de aplicativos, mas para o que cria bibliotecas que serão usadas posteriormente por outras pessoas que desejam configurar o comportamento do código da biblioteca ou daquele que usa bibliotecas. Portanto, o contexto de usar efetivamente uma expressão lambda é o uso / criação de uma biblioteca / estrutura.

Além disso, como eles descrevem o código de uso único, eles não precisam ser membros de uma classe em que isso levará a mais complexidade de código. Imagine ter que declarar uma classe com foco claro toda vez que quisermos configurar a operação de um objeto de classe.

Grigoris Dimitroulakos
fonte