Share via


Expressões lambda (Guia de Programação em C#)

Uma expressão lambda é uma função anônima que você pode usar para criar delegados ou tipos árvore de expressão. Ao usar expressões lambda, você pode escrever funções locais que podem ser passadas como argumentos ou retornadas como o valor de chamadas de função. Expressões lambda são particularmente úteis para escrever expressões de consulta LINQ.

Para criar uma expressão lambda, especifique os parâmetros de entrada (se houver) no lado esquerdo do operador lambda => e coloque a expressão ou o bloco de instruções do outro lado. Por exemplo, a expressão lambda x => x * x especifica um parâmetro chamado x e retorna o valor de x ao quadrado. Você pode atribuir essa expressão a um tipo delegado como neste exemplo:

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25
}

Para criar um tipo de árvore de expressão:

using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}

O operador => tem a mesma precedência que a atribuição (=) e é associativo direito (consulte a seção "Associatividade" do artigo Operadores).

Lambdas são usadas em consultas LINQ baseadas em métodos como argumentos para métodos de operador de consulta padrão como Where``1.

Quando você usa a sintaxe baseada em método para chamar o método Where``1 na classe Enumerable (assim como você faz em LINQ para objetos e LINQ to XML) o parâmetro é um tipo delegado Func. Uma expressão lambda é a maneira mais conveniente de criar esse delegado. Quando você chama o mesmo método na, por exemplo, classe Queryable (como faz em LINQ to SQL), o tipo de parâmetro é Expression<Func>. onde Func é qualquer delegado Func com até 16 parâmetros de entrada. Novamente, uma expressão lambda é apenas uma maneira muito concisa de construir essa árvore de expressão. Os lambdas permitem que chamadas Where pareçam semelhantes embora, na verdade, o tipo de objeto criado do lambda seja diferente.

No exemplo anterior, observe que a assinatura do delegado tem um parâmetro de entrada implícito inserido do tipo int e retorna um int. A expressão lambda pode ser convertida como um delegado desse tipo porque também tem um parâmetro de entrada (x) e um valor de retorno que o compilador pode converter implicitamente no tipo int. (A inferência de tipos é discutida em mais detalhes nas seções a seguir.) Quando o delegado for chamado usando um parâmetro de entrada 5, ele retornará 25 como resultado.

Lambdas não são permitidos no lado esquerdo do operador is ou as.

Todas as restrições que se aplicam a métodos anônimos também se aplicam às expressões lambda. Para obter mais informações, consulte Métodos anônimos (Guia de Programação em C#).

Lambdas de expressão

Uma expressão lambda com uma expressão no lado direito do operador => é chamada de lambda da expressão. Os lambdas de expressão são usados amplamente na construção de Árvores de expressão (C# e Visual Basic). Uma expressão lambda retorna o resultado da expressão e tem o seguinte formato básico:

(input parameters) => expression

Os parênteses serão opcionais somente se o lambda tiver um parâmetro de entrada; caso contrário, eles serão obrigatórios. Dois ou mais parâmetros de entrada são separados por vírgulas e envolvidos por parênteses:

(x, y) => x == y

Às vezes, é difícil ou impossível para o compilador inferir os tipos de entrada. Quando isso ocorre, você pode especificar os tipos de maneira explícita conforme mostrado neste exemplo:

(int x, string s) => s.Length > x

Especifique parâmetros de entrada zero com parênteses vazios:

() => SomeMethod()

Observe no exemplo anterior que o corpo de uma expressão lambda pode consistir de uma chamada de método. No entanto, se você estiver criando árvores de expressão que serão avaliadas fora do .NET Framework, como no SQL Server, você não deverá usar chamadas de método em expressões lambda. Os métodos não terão significado fora do contexto do .NET Common Language Runtime.

Lambdas de instrução

Um lambda de instrução lembra um lambda de expressão, exceto que as instruções estão incluídas entre chaves:

(input parameters) => {statement;}

O corpo de uma instrução lambda pode consistir de qualquer número de instruções; no entanto, na prática, normalmente não há mais de duas ou três.

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

Lambdas de instrução, como métodos anônimos, não podem ser usados para criar árvores de expressão.

Lambdas assíncronos

Você pode facilmente criar expressões e instruções lambda que incorporem processamento assíncrono ao usar as palavras-chaves async e await. Por exemplo, o exemplo do Windows Forms a seguir contém um manipulador de eventos que chama e espera um método assíncrono ExampleMethodAsync.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // ExampleMethodAsync returns a Task.
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
    }

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Você pode adicionar o mesmo manipulador de eventos ao usar um lambda assíncrono. Para adicionar esse manipulador, adicione um modificador async antes da lista de parâmetros lambda, como mostra o exemplo a seguir.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            // ExampleMethodAsync returns a Task.
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
        };
    }

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Para obter mais informações sobre como criar e usar métodos assíncronos, consulte Programação assíncrona com Async e Await (C# e Visual Basic).

Lambdas com os operadores de consulta padrão

Vários operadores de consulta padrão têm um parâmetro de entrada cujo tipo é de uma família Func de delegados genéricos. Esses delegados usam parâmetros de tipo para definir o número e os tipos de parâmetros de entrada e o tipo de retorno do delegado. delegados Func são muito úteis para encapsular expressões definidas pelo usuário aplicadas a cada elemento em um conjunto de dados de origem. Por exemplo, considere o seguinte tipo delegado:

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

O delegado pode ser instanciado como Func<int,bool> myFunc onde int é um parâmetro de entrada e bool é o valor de retorno. O valor de retorno é sempre especificado no último parâmetro de tipo. Func<int, string, bool> define um delegado com dois parâmetros de entrada, int e string e um tipo de retorno bool. O delegado Func a seguir, quando é chamado, retornará true ou false para indicar se o parâmetro de entrada é igual a 5:

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course

Você também pode fornecer uma expressão lambda quando o tipo de argumento é Expression<Func>, por exemplo, nos operadores de consulta padrão que são definidos em System.Linq.Queryable. Quando você especifica um argumento Expression<Func>, o lambda será compilado em uma árvore de expressão.

Um operador de consulta padrão, o método Count``1, é mostrado aqui:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

O compilador pode inferir o tipo de parâmetro de entrada ou você também pode especificá-lo explicitamente. Essa expressão lambda em particular conta esses inteiros (n) que, quando dividida por dois, tem um resto 1.

A linha de código a seguir gera uma sequência que contém todos os elementos na matriz numbers do lado esquerdo do 9 porque esse é o primeiro número na sequência que não atende à condição:

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

Este exemplo mostra como especificar vários parâmetros de entrada ao inclui-los entre parênteses. O método retorna todos os elementos na matriz de números até ser encontrado um número cujo valor seja menor do que sua posição. Não confunda o operador lambda (=>) com o operador greater than ou equal (>=).

var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

Inferência de tipos em lambdas

Ao escrever lambdas, você geralmente não precisa especificar um tipo para os parâmetros de entrada porque o compilador pode inferir o tipo com base no corpo lambda, no tipo delegado do parâmetro e em outros fatores conforme descrito na especificação da linguagem C#. Para a maioria dos operadores de consulta padrão, a primeira entrada é o tipo dos elementos na sequência de origem. Portanto, se você estiver consultando um IEnumerable<Customer>, a variável de entrada será inferida para ser um objeto Customer, o que significa que você tem acesso aos seus métodos e propriedades:

customers.Where(c => c.City == "London");

As regras gerais para lambdas são:

  • O lambda deve conter o mesmo número de parâmetros do tipo delegado.

  • Cada parâmetro de entrada no lambda deve ser implicitamente conversível em seu parâmetro delegado correspondente.

  • O valor de retorno do lambda (se houver) deve ser implicitamente conversível para o tipo de retorno do delegado.

Observe que as expressões lambda em si não têm um tipo porque o sistema de tipo comum não possui conceito intrínseco da "expressão lambda". No entanto, às vezes é conveniente falar informalmente do "tipo" de uma expressão lambda. Nesses casos, o tipo se refere ao tipo delegado ou tipo Expression ao qual a expressão lambda é convertida.

Escopo variável em expressões lambda

As lambdas podem se referir a variáveis externas (consulte Métodos anônimos (Guia de Programação em C#)) que estão no escopo do método que define a função lambda ou no escopo do tipo que contém a expressão lambda. As variáveis que são capturadas dessa forma são armazenadas para uso na expressão lambda mesmo que de alguma outra forma elas saíssem do escopo e fossem coletadas como lixo. Uma variável externa deve ser definitivamente atribuída para que possa ser consumida em uma expressão lambda. O exemplo a seguir demonstra estas regras:

delegate bool D();
delegate bool D2(int i);

class Test
{
    D del;
    D2 del2;
    public void TestMethod(int input)
    {
        int j = 0;
        // Initialize the delegates with lambda expressions.
        // Note access to 2 outer variables.
        // del will be invoked within this method.
        del = () => { j = 10;  return j > input; };

        // del2 will be invoked after TestMethod goes out of scope.
        del2 = (x) => {return x == j; };
      
        // Demonstrate value of j:
        // Output: j = 0 
        // The delegate has not been invoked yet.
        Console.WriteLine("j = {0}", j);        // Invoke the delegate.
        bool boolResult = del();

        // Output: j = 10 b = True
        Console.WriteLine("j = {0}. b = {1}", j, boolResult);
    }

    static void Main()
    {
        Test test = new Test();
        test.TestMethod(5);

        // Prove that del2 still has a copy of
        // local variable j from TestMethod.
        bool result = test.del2(10);

        // Output: True
        Console.WriteLine(result);
           
        Console.ReadKey();
    }
}

As seguintes regras se aplicam ao escopo variável em expressões lambda:

  • Uma variável capturada não será coletada do lixo até que o delegado que faz referência a ela se qualifique para coleta de lixo.

  • As variáveis introduzidas em uma expressão lambda não são visíveis no método externo.

  • Uma expressão lambda não pode capturar diretamente um parâmetro ref ou out de um método delimitador.

  • Uma instrução return em uma expressão lambda não faz com que o método delimitador retorne.

  • Uma expressão lambda não poderá conter uma instrução goto, break ou continue que está dentro da função lambda se o destino da instrução jump estiver fora do bloco. Também será um erro ter uma instrução jump fora do bloco da função lambda se o destino estiver dentro do bloco.

Especificação da Linguagem C#

Para obter mais informações, consulte a Especificação da linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Capítulo do Livro em Destaque

Delegates, Events, and Lambda Expressions em C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers.

Consulte também

Referência

Métodos anônimos (Guia de Programação em C#)

is (Referência de C#)

Conceitos

Guia de Programação em C#

Árvores de expressão (C# e Visual Basic)

Outros recursos

LINQ (Consulta Integrada à Linguagem)

Exemplos de C# do Visual Studio 2008 (veja arquivos de consultas LINQ e XQuery de exemplo)

Expressões lambda recursivas