Compartilhar via


Expressões lambda e funções anônimas

Use 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. Os tipos de parâmetros e o valor retornado definem o tipo delegado no qual uma expressão lambda pode ser convertida. 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 seguinte exemplo, a expressão lambda x => x * x, que especifica um parâmetro chamado x e retorna o valor de x quadrado, é atribuída a uma variável de um tipo delegado:

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

As expressões lambdas também podem ser convertidas nos tipos de árvore de expressão, como mostra o seguinte exemplo:

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

Você usa expressões lambda em qualquer código que exija instâncias de tipos delegados ou árvores de expressão. Um exemplo é o argumento para o método Task.Run(Action) para passar o código que deve ser executado em segundo plano. Use também expressões lambda ao gravar LINQ no C#, conforme mostrado no 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 método Queryable.Select na classe System.Linq.Queryable, por exemplo, no 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 serão avaliadas fora contexto do .NET CLR (Common Language Runtime), como no SQL Server, você não deverá usar chamadas de método em lambdas de expressão. Os métodos não têm significado fora do contexto do .NET CLR (Common Language Runtime).

Lambdas de instrução

Um lambda de instrução é semelhante a um lambda de expressão, exceto que as instruções são colocadas 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 os lambdas de instrução para criar árvores de expressão.

Parâmetros de entrada de uma expressão lambda

Coloque os 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.

Use discards 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 do 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 chamado de _, em uma expressão lambda, _ será tratado como o nome desse parâmetro.

Começando no C# 12, você pode fornecer valores padrão para parâmetros em expressões lambda. A sintaxe e as restrições em valores de parâmetro padrão são iguais às dos métodos e das funções locais. O exemplo a seguir declara uma expressão lambda com um parâmetro padrão e, em seguida, a chama uma vez usando o padrão e uma vez com dois parâmetros explícitos:

var IncrementBy = (int source, int increment = 1) => source + increment;

Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7

Você também pode declarar expressões lambda com matrizes ou coleções params como parâmetros:

var sum = (params IEnumerable<int> values) =>
{
    int sum = 0;
    foreach (var value in values) 
        sum += value;
    
    return sum;
};

var empty = sum();
Console.WriteLine(empty); // 0

var sequence = new[] { 1, 2, 3, 4, 5 };
var total = sum(sequence);
Console.WriteLine(total); // 15

Como parte dessas atualizações, quando um grupo de métodos que tem um parâmetro padrão é atribuído a uma expressão lambda, essa expressão lambda também tem o mesmo parâmetro padrão. Um grupo de métodos com um parâmetro de coleção params também pode ser atribuído a uma expressão lambda.

Expressões lambda com parâmetros padrão ou coleções params como parâmetros não têm tipos naturais que correspondem a tipos Func<> ou Action<>. No entanto, você pode definir tipos delegados que incluem valores de parâmetro padrão:

delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);
delegate int SumCollectionDelegate(params IEnumerable<int> values);

Ou você pode usar variáveis de tipo implícito com declarações var para definir o tipo delegado. O compilador sintetiza o tipo de delegado correto.

Para obter mais informações sobre parâmetros padrão em expressões lambda, confira a especificação de recurso para parâmetros padrão em expressões lambda.

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 os métodos assíncronos, consulte Programação assíncrona com async e await.

Expressões lambda e tuplas

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 chamados de Item1, Item2 e assim por diante. No entanto, você pode definir uma tupla com componentes nomeados, como é feito no 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 saber mais sobre as tuplas do C#, confira Tipos de tuplas.

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 ú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

Você não usa expressões lambda diretamente em expressões de consulta, mas pode usá-las em chamadas de método nas expressões de consulta, conforme mostrado no exemplo a seguir:

var numberSets = new List<int[]>
{
    new[] { 1, 2, 3, 4, 5 },
    new[] { 0, 0, 0 },
    new[] { 9, 8 },
    new[] { 1, 0, 1, 0, 1, 0, 1, 0 }
};

var setsWithManyPositives = 
    from numberSet in numberSets
    where numberSet.Count(n => n > 0) > 3
    select numberSet;

foreach (var numberSet in setsWithManyPositives)
{
    Console.WriteLine(string.Join(" ", numberSet));
}
// Output:
// 1 2 3 4 5
// 1 0 1 0 1 0 1 0

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.

Tipo natural de uma expressão lambda

Uma expressão lambda em si não tem um tipo, pois o sistema de tipo comum não tem conceitos intrínsecos de "expressão lambda". No entanto, às vezes convém falar informalmente do "tipo" de uma expressão lambda. Esse "tipo" informal se refere ao tipo delegado ou tipo Expression ao qual a expressão lambda é convertida.

A partir do C# 10, uma expressão lambda pode ter um tipo natural. Em vez de forçar você a declarar um tipo delegado, como Func<...> ou Action<...>, para uma expressão lambda, o compilador pode inferir o tipo delegado da expressão lambda. Por exemplo, considere a seguinte declaração:

var parse = (string s) => int.Parse(s);

O compilador pode inferir parse para ser um Func<string, int>. O compilador escolhe um delegado Func ou Action disponível, se houver um adequado. Caso contrário, ele sintetiza um tipo delegado. Por exemplo, o tipo delegado será sintetizado se a expressão lambda tiver os parâmetros ref. Quando uma expressão lambda tem um tipo natural, pode ser atribuída a um tipo menos explícito, como System.Object ou System.Delegate:

object parse = (string s) => int.Parse(s);   // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

Os grupos de métodos (ou seja, nomes de método sem listas de parâmetros) com exatamente uma sobrecarga têm um tipo natural:

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose

Se você atribuir uma expressão lambda a System.Linq.Expressions.LambdaExpression ou System.Linq.Expressions.Expression e o lambda tiver um tipo delegado natural, a expressão terá um tipo natural de System.Linq.Expressions.Expression<TDelegate>, e o tipo delegado natural será usado como o argumento para o parâmetro de tipo:

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s);       // Expression<Func<string, int>>

Nem todas as expressões lambda têm um tipo natural. Considere a seguinte declaração:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

O compilador não pode inferir um tipo de parâmetro para s. Quando o compilador não pode inferir um tipo natural, você deve declarar o tipo:

Func<string, int> parse = s => int.Parse(s);

Tipo de retorno explícito

Normalmente, o tipo de retorno de uma expressão lambda é óbvio e inferido. Para algumas expressões, isso não dá certo:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

A partir do C# 10, você pode especificar o tipo de retorno de uma expressão lambda, antes dos parâmetros de entrada. Ao especificar um tipo de retorno explícito, você deve colocar entre parênteses os parâmetros de entrada:

var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>

Atributos

A partir do C# 10, você pode adicionar atributos a uma expressão lambda e seus parâmetros. O exemplo a seguir mostra como adicionar atributos a uma expressão lambda:

Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;

Você também pode adicionar atributos aos parâmetros de entrada ou ao valor de retorno, conforme mostrado no exemplo a seguir:

var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;

Conforme mostrado nos exemplos anteriores, você deve colocar entre parênteses os parâmetros de entrada, ao adicionar atributos a uma expressão lambda ou seus parâmetros.

Importante

As expressões lambda são invocadas por meio do tipo delegado subjacente. É diferente dos métodos e funções locais. O método Invoke do delegado não verifica os atributos na expressão lambda. Os atributos não têm efeito quando a expressão lambda é invocada. Atributos das expressões lambda são úteis para análise de código e podem ser descobertos por meio da reflexão. Uma consequência dessa decisão é que System.Diagnostics.ConditionalAttribute não pode ser aplicado a uma expressão lambda.

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

Os lambdas pode fazer referência a variáveis externas. Essas variáveis externas 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.

Aplique o modificador static a uma expressão lambda para evitar a captura não intencional de variáveis locais ou do estado da instância pelo lambda:

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

Um lambda estático não pode capturar variáveis locais ou o estado da instância dos escopos delimitadores, mas pode fazer referência a membros estáticos e definições constantes.

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 esses recursos, confira as seguintes notas sobre a proposta de recurso:

Confira também