Expressões lambda (referência de C#)

Você usa uma expressão lambda para criar uma função anônima. Use o operador de declaração lambda => para separar a lista de parâmetros de lambda do corpo. Uma expressão lambda pode ser de qualquer uma das duas formas a seguir:

  • Expressão lambda que tem uma expressão como corpo:

    (input-parameters) => expression
    
  • Instrução lambda que tem um bloco de instrução como corpo:

    (input-parameters) => { <sequence-of-statements> }
    

Para criar uma expressão lambda, especifique os parâmetros de entrada (se houver) no lado esquerdo do operador lambda e uma expressão ou um bloco de instrução do outro lado.

Qualquer expressão lambda pode ser convertida para um tipo delegado. O tipo delegado no qual uma expressão lambda pode ser convertida é definido pelos tipos de parâmetros e pelo valor retornado. Se uma expressão lambda não retornar um valor, ela poderá ser convertida em um dos tipos delegados Action; caso contrário, ela poderá ser convertida em um dos tipos delegados Func. Por exemplo, uma expressão lambda que tem dois parâmetros e não retorna nenhum valor pode ser convertida em um delegado Action<T1,T2>. Uma expressão lambda que tem um parâmetro e retorna um valor pode ser convertida em um delegado Func<T,TResult>. No exemplo a seguir, a expressão lambda , que especifica um parâmetro chamado e retorna o valor de ao quadrado, é atribuída a uma variável de um x => x * x x tipo x delegado:

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25

Lambdas de expressão também podem ser convertidos nos tipos de árvore de expressão, como mostra o exemplo a seguir:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

Use expressões lambda em qualquer código que exija instâncias de tipos delegados ou árvores de expressão, por exemplo, como um argumento ao método Task.Run(Action) para passar o código que deve ser executado em segundo plano. Você também pode usar expressões lambda ao escrever LINQ em C#,como mostra o exemplo a seguir:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25

Quando você usa a sintaxe baseada em método para chamar o método Enumerable.Select na classe System.Linq.Enumerable, por exemplo, no LINQ to Objects e no LINQ to XML, o parâmetro é um tipo delegado System.Func<T,TResult>. Quando você chama o Queryable.Select método na classe , por System.Linq.Queryable exemplo, em LINQ to SQL, o tipo de parâmetro é um tipo de árvore de expressão Expression<Func<TSource,TResult>> . Em ambos os casos, você pode usar a mesma expressão lambda para especificar o valor do parâmetro. Isso faz com que as duas chamadas Select pareçam semelhantes, embora, na verdade, o tipo de objetos criado dos lambdas seja diferente.

Lambdas de expressão

Uma expressão lambda com uma expressão no lado direito do operador => é chamada de lambda de expressão. Uma expressão lambda retorna o resultado da expressão e tem o seguinte formato básico:

(input-parameters) => expression

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

Lambdas de instrução

Uma instrução lambda é semelhante a uma expressão lambda, exceto que suas instruções estão entre chaves:

(input-parameters) => { <sequence-of-statements> }

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.

Action<string> greet = name =>
{
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

Você não pode usar lambdas de instrução para criar árvores de expressão.

Parâmetros de entrada de uma expressão lambda

Você inclui parâmetros de entrada de uma expressão lambda entre parênteses. Especifique parâmetros de entrada zero com parênteses vazios:

Action line = () => Console.WriteLine();

Se uma expressão lambda tiver apenas um parâmetro de entrada, os parênteses serão opcionais:

Func<double, double> cube = x => x * x * x;

Dois ou mais parâmetros de entrada são separados por vírgulas:

Func<int, int, bool> testForEquality = (x, y) => x == y;

Às vezes, o compilador não pode inferir os tipos de parâmetros de entrada. Você pode especificar os tipos de maneira explícita conforme mostrado neste exemplo:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

Os tipos de parâmetro de entrada devem ser todos explícitos ou implícitos; caso contrário, ocorrerá o erro CS0748 de compilador.

A partir do C# 9.0, você pode usar descartes para especificar dois ou mais parâmetros de entrada de uma expressão lambda que não são usados na expressão:

Func<int, int, int> constant = (_, _) => 42;

Os parâmetros de descarte lambda podem ser úteis quando você usa uma expressão lambda para fornecer um manipulador de eventos.

Observação

Para compatibilidade com versões anteriores, se apenas um único parâmetro de entrada for nomeado _ , em seguida, dentro de uma expressão lambda, _ será tratado como o nome desse parâmetro.

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();
        button1.Click += button1_Click;
    }

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

    private 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) =>
        {
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\n";
        };
    }

    private 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.

Expressões lambda e tuplas

A partir do C# 7.0, a linguagem C# fornece suporte interno para tuplas. Você pode fornecer uma tupla como um argumento para uma expressão lambda e a expressão lambda também pode retornar uma tupla. Em alguns casos, o compilador do C# usa a inferência de tipos para determinar os tipos dos componentes da tupla.

Você pode definir uma tupla, colocando entre parênteses uma lista delimitada por vírgulas de seus componentes. O exemplo a seguir usa a tupla com três componentes para passar uma sequência de números para uma expressão lambda, que dobra cada valor e retorna uma tupla com três componentes que contém o resultado das multiplicações.

Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)

Normalmente, os campos de uma tupla são nomeados Item1 , Item2 etc. No entanto, você pode definir uma tupla com componentes nomeados, como faz o exemplo a seguir.

Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");

Para obter mais informações sobre tuplas C#, consulte tipos de tupla.

Lambdas com os operadores de consulta padrão

O LINQ to Objects, entre outras implementações, tem um parâmetro de entrada cujo tipo faz parte da família de delegados genéricos Func<TResult>. Esses delegados usam parâmetros de tipo para definir o número e o tipo 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 Func<T,TResult>:

public delegate TResult Func<in T, out TResult>(T arg)

O delegado pode ser instanciado como um Func<int, bool>, em que int é um parâmetro de entrada e bool é o valor de retorno. O valor de retorno é sempre especificado no último parâmetro de tipo. Por exemplo, Func<int, string, bool> define um delegado com dois parâmetros de entrada, int e string, e um tipo de retorno de bool. O delegado Func a seguir, quando é invocado, retornará um valor booliano que indica se o parâmetro de entrada é ou não igual a cinco:

Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result);   // False

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

O exemplo a seguir usa o operador padrão de consulta Count:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");

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.

O exemplo a seguir gera uma sequência que contém todos os elementos da matriz numbers que precedem o 9, porque esse é o primeiro número na sequência que não satisfaz a condição:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3

O exemplo a seguir especifica vários parâmetros de entrada, colocando-os entre parênteses. O método retorna todos os elementos na matriz numbers até encontrar um número cujo valor seja inferior à sua posição ordinal na matriz:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4

Inferência de tipos em expressões lambda

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 do lambda, nos tipos de 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. Se você estiver consultando um IEnumerable<Customer>, a variável de entrada será inferida para ser um objeto Customer, o que significa que você terá acesso aos seus métodos e propriedades:

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

As regras gerais para a inferência de tipos para lambdas são as seguintes:

  • 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 nenhum conceito intrínseco de "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.

Captura de variáveis externas e escopo variável em expressões lambda

Os lambdas pode fazer referência a variáveis externas. Essas são as variáveis que estão no escopo do método que define a expressã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:

public static class VariableScopeWithLambdas
{
    public class VariableCaptureGame
    {
        internal Action<int> updateCapturedLocalVariable;
        internal Func<int, bool> isEqualToCapturedLocalVariable;

        public void Run(int input)
        {
            int j = 0;

            updateCapturedLocalVariable = x =>
            {
                j = x;
                bool result = j > input;
                Console.WriteLine($"{j} is greater than {input}: {result}");
            };

            isEqualToCapturedLocalVariable = x => x == j;

            Console.WriteLine($"Local variable before lambda invocation: {j}");
            updateCapturedLocalVariable(10);
            Console.WriteLine($"Local variable after lambda invocation: {j}");
        }
    }

    public static void Main()
    {
        var game = new VariableCaptureGame();

        int gameInput = 5;
        game.Run(gameInput);

        int jTry = 10;
        bool result = game.isEqualToCapturedLocalVariable(jTry);
        Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");

        int anotherJ = 3;
        game.updateCapturedLocalVariable(anotherJ);

        bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
        Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
    }
    // Output:
    // Local variable before lambda invocation: 0
    // 10 is greater than 5: True
    // Local variable after lambda invocation: 10
    // Captured local variable is equal to 10: True
    // 3 is greater than 5: False
    // Another lambda observes a new value of captured variable: True
}

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 delimitador.

  • Uma expressão lambda não pode capturar um parâmetro in, ref ou out diretamente 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 pode conter uma instrução goto, break ou continue se o destino daquela instrução de salto estiver fora do bloco da expressão lambda. Também será um erro ter uma instrução de salto fora do bloco da expressão lambda se o destino estiver dentro do bloco.

A partir do C# 9,0, você pode aplicar o static modificador a uma expressão lambda para impedir a captura não intencional de variáveis locais ou o estado de instância pelo lambda:

Func<double, double> square = static x => x * x;

Um lambda estático não pode capturar variáveis locais ou estado de instância de escopos delimitadores, mas pode referenciar membros estáticos e definições de constante.

Especificação da linguagem C#

Para obter mais informações, confira a seção Expressões de função anônima da Especificação da linguagem C#.

Para obter mais informações sobre os recursos adicionados em C# 9,0, consulte as seguintes notas de proposta de recurso:

Confira também