Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
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 do lambda do seu corpo.
A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em versões preliminares públicas para a próxima versão da linguagem.
A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.
Sugestão
Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.
Uma expressão lambda pode ser qualquer uma das seguintes duas formas:
Expressão lambda que tem uma expressão como o seu corpo:
(input-parameters) => expressionStatement lambda que tem um bloco de instrução como o seu corpo:
(input-parameters) => { <sequence-of-statements> }
Para criar uma expressão lambda, especifique parâmetros de entrada (se existirem) no lado esquerdo do operador lambda e uma expressão ou bloco de instruções do outro lado.
Pode converter qualquer expressão lambda para um tipo delegado . Os tipos de seus parâmetros e valor de retorno definem o tipo de delegado para o qual uma expressão lambda pode ser convertida. Se uma expressão lambda não devolver um valor, converte-a para um dos Action tipos de delegado. Se devolver um valor, converte-o para um dos Func tipos de delegado. Por exemplo, converter uma expressão lambda que tem dois parâmetros e não devolve valor a um Action<T1,T2> delegado. Converte uma expressão lambda que tenha um parâmetro e devolve um valor a um Func<T,TResult> delegado. No exemplo a seguir, 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
Também pode converter lambdas de expressões para os tipos de árvore de expressão , como mostra o exemplo seguinte:
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 de delegados ou árvores de expressões. Um exemplo é o argumento para o 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 sintaxe baseada em método para chamar o método Enumerable.Select na classe System.Linq.Enumerable, por exemplo, em LINQ to Objects e 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, 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 criados a partir das lambdas seja diferente.
Expressão lambdas
Uma expressão lambda com uma expressão no lado direito do operador => é chamada de expressão lambda . Uma expressão lambda retorna o resultado da expressão e assume a seguinte forma básica:
(input-parameters) => expression
O corpo de uma expressão lambda pode consistir numa invocação de método. No entanto, quando crias árvores de expressão que um fornecedor de consulta avalia, limita as chamadas de método aos métodos que o fornecedor de consulta traduz para o seu formato. Diferentes fornecedores de consultas têm capacidades variadas. Por exemplo, muitos fornecedores baseados em SQL podem traduzir métodos como String.StartsWith em expressões SQL apropriadas, como LIKE. Se um provedor de consulta não reconhecer uma chamada de método, ele não poderá traduzir ou executar a expressão.
Declaração lambdas
Uma instrução lambda se assemelha a uma expressão lambda, exceto que suas instruções estão entre chaves:
(input-parameters) => { <sequence-of-statements> }
O corpo de uma afirmação lambda pode consistir em qualquer número de afirmações. No entanto, na prática, normalmente não há mais do que dois ou três.
Action<string> greet = name =>
{
string greeting = $"Hello {name}!";
Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!
Não é possível usar lambdas de instrução para criar árvores de expressão.
Parâmetros de entrada de uma expressão lambda
Inclua parâmetros de entrada de uma expressão lambda entre parênteses. Especifique parâmetros de entrada zero usando parênteses vazios:
Action line = () => Console.WriteLine();
Se uma expressão lambda tiver apenas um parâmetro de entrada, pode omitir os parênteses:
Func<double, double> cube = x => x * x * x;
Separe dois ou mais parâmetros de entrada com vírgulas:
Func<int, int, bool> testForEquality = (x, y) => x == y;
O compilador normalmente infere os tipos de parâmetros para expressões lambda, o que é chamado de lista de parâmetros tipada implicitamente. Podes especificar os tipos explicitamente, o que é chamado de lista de parâmetros explicitamente tipada. O exemplo seguinte mostra uma lista de parâmetros explicitamente tipada:
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
Os tipos de parâmetros de entrada devem ser todos explícitos ou todos implícitos. Caso contrário, ocorre um erro do compilador CS0748 . Antes do C# 14, você deve incluir o tipo explícito em um parâmetro se ele tiver algum modificador, como ref ou out. No C# 14, essa restrição é removida. No entanto, você ainda deve declarar o tipo se você usar o params modificador.
Use 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 do Lambda podem ser úteis quando você usa uma expressão lambda para fornecer um manipulador de eventos.
Observação
Para compatibilidade retroativa, se apenas um único parâmetro de entrada for nomeado _, o compilador trata _ como o nome desse parâmetro dentro dessa expressão lambda.
A partir de C# 12, pode fornecer valores predefinidos para listas de parâmetros explicitamente tipadas. A sintaxe e as restrições nos valores de parâmetros padrão são as mesmas que para métodos e funções locais. O exemplo seguinte declara uma expressão lambda com um parâmetro por defeito, depois chama-a uma vez usando o padrão e outra 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 params matrizes ou coleções como último parâmetro numa lista de parâmetros tipada explicitamente:
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 coleta params também pode ser atribuído a uma expressão lambda.
As expressões do Lambda com parâmetros padrão ou coleções params como parâmetros não têm tipos naturais que correspondam a tipos Func<> ou Action<>. No entanto, você pode definir tipos delegados que incluam 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 digitadas implicitamente com declarações var para definir o tipo de delegado. O compilador sintetiza o tipo de delegado correto.
Para obter mais informações sobre parâmetros padrão em expressões lambda, consulte a especificação de recurso para parâmetros padrão em expressões lambda.
Lambdas assíncronas
Ao usar as palavras-chave assíncronas e await , pode facilmente criar expressões e instruções lambda que incorporam processamento assíncrono. Por exemplo, o exemplo de Windows Forms a seguir contém um manipulador de eventos que chama e aguarda 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 usando um lambda assíncrono. Para adicionar esse manipulador, adicione um modificador de 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 assíncrono e aguarde.
Expressões lambda e tuplas
A linguagem C# fornece suporte nativo para tuplas. Você pode fornecer uma tupla como argumento para uma expressão lambda, e sua expressão lambda também pode retornar uma tupla. Em alguns casos, o compilador C# recorre à inferência de tipos para determinar os tipos dos elementos de uma tupla.
Você define uma tupla colocando entre parênteses uma lista delimitada por vírgulas de seus componentes. O exemplo a seguir usa 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 designados Item1, Item2e assim por diante. 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 mais informações sobre tipos de tuplas em C#, consulte Tipos de Tupla.
Lambdas com os operadores de consulta padrão
O LINQ to Objects, entre outras implementações, utiliza um parâmetro de entrada cujo tipo pertence à Func<TResult> família de delegados genéricos. 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.
Func delegados são úteis para encapsular expressões definidas pelo usuário que são aplicadas a cada elemento em um conjunto de dados de origem. Por exemplo, considere o tipo de delegado Func<T,TResult>:
public delegate TResult Func<in T, out TResult>(T arg)
Instancias o delegado como uma Func<int, bool> instância onde int é um parâmetro de entrada e bool é o valor de retorno. O valor de retorno é sempre especificado no último parâmetro type. 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 seguinte Func delegado, quando invocado, devolve um valor booleano que indica se o parâmetro de entrada é igual a cinco:
Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result); // False
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 o Queryable tipo define. Quando você especifica um argumento Expression<TDelegate>, o lambda é compilado em uma árvore de expressão.
O exemplo a seguir usa o operador de consulta padrão 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 do parâmetro de entrada, ou pode especificá-lo explicitamente. Esta expressão lambda em particular conta os inteiros (n) que, quando divididos por dois, têm um restante de 1.
O exemplo a seguir produz uma sequência que contém todos os elementos na matriz numbers que precedem o 9, porque esse é o primeiro número na sequência que não atende à 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 é menor do que 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 dentro de expressões de consulta, como mostra o 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 tipo em expressões lambda
Quando escreves lambdas, muitas vezes não precisas de especificar um tipo para os parâmetros de entrada. O compilador infere o tipo com base no corpo lambda, nos tipos de parâmetros e noutros fatores descritos 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 estiver a consultar um IEnumerable<Customer>, a variável de entrada é inferida como um Customer objeto, o que significa que pode aceder aos seus métodos e propriedades:
customers.Where(c => c.City == "London");
As regras gerais para inferência de tipos para lambdas são:
- O lambda deve conter o mesmo número de parâmetros que o 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 não tem um tipo porque o sistema de tipos comum não tem um conceito intrínseco de "expressão lambda". No entanto, por vezes é conveniente falar informalmente do "tipo" de uma expressão lambda. Esse "tipo" informal refere-se ao tipo delegado ou ao tipo Expression ao qual a expressão lambda é convertida.
Uma expressão lambda pode ter um tipo natural . Em vez de exigir que declare um tipo de delegado, como Func<...> ou Action<...> para uma expressão lambda, o compilador infere o tipo de delegado a partir da expressão lambda. Por exemplo, considere a seguinte declaração:
var parse = (string s) => int.Parse(s);
O compilador infere parse que é um Func<string, int>. O compilador escolhe um delegado Func ou Action disponível, se existir um adequado. Caso contrário, ele sintetiza um tipo de delegado. Por exemplo, o tipo de delegado é sintetizado quando a expressão lambda possui ref parâmetros. Quando uma expressão lambda tem um tipo natural, pode atribuí-la 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>
Grupos de métodos (ou seja, nomes de métodos 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 de delegado natural, a expressão terá um tipo natural de System.Linq.Expressions.Expression<TDelegate>, com o tipo de delegado natural usado como argumento para o parâmetro type:
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, essa inferência não funciona:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
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
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 valor de retorno, como mostra o 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;
Como mostram os 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
Invocas expressões lambda através do tipo de delegado subjacente. Esta invocação é diferente dos métodos e funções locais. O método Invoke do delegado não verifica atributos na expressão lambda. Os atributos não têm qualquer efeito quando a expressão lambda é invocada. Os atributos em expressões lambda são úteis para análise de código e podem ser descobertos por meio de reflexão. Uma consequência desta decisão é que o System.Diagnostics.ConditionalAttribute não pode ser aplicado a uma expressão lambda.
Captura de variáveis externas e escopo de variáveis em expressões lambda
Lambdas podem referir-se às variáveis externas. Essas variáveis externas são as variáveis que estão no escopo no método que define a expressão lambda, ou no escopo no tipo que contém a expressão lambda. Se capturar variáveis desta forma, a expressão lambda armazena-as para uso mesmo que as variáveis ultrapassem o âmbito e normalmente seriam recolhidas de lixo. Deves definitivamente atribuir uma variável externa antes de a consumir numa expressão lambda. O exemplo a seguir demonstra essas 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 regras a seguir se aplicam ao escopo variável em expressões lambda:
- Uma variável que capturas não é recolhida de lixo até que o delegado que a faz referência se torne elegível para recolha de lixo.
- As variáveis que introduz dentro de uma expressão lambda não são visíveis no método de encerramento.
- Uma expressão lambda não pode capturar diretamente um parâmetro em, refou um parâmetro out do método delimitador.
- Uma instrução de retorno em uma expressão lambda não faz com que o método envolvente retorne.
- Uma expressão lambda não pode conter uma instrução goto, breakou instrução de continuar se o destino dessa instrução de salto estiver fora do bloco da expressão lambda. Também é um erro ter uma instrução de salto fora do bloco de expressão lambda se o destino estiver dentro do bloco.
Para evitar a captura não intencional de variáveis locais ou do estado da instância pelo lambda, aplique o static modificador a uma expressão lambda:
Func<double, double> square = static x => x * x;
Um lambda estático não consegue capturar variáveis locais ou estados de instância a partir de escopos envolventes, mas pode referenciar membros estáticos e definições constantes.
Especificação da linguagem C#
Para obter mais informações, consulte a seção Expressões de Função Anónimas da Especificação da Linguagem C#.
Para obter mais informações sobre esses recursos, consulte as seguintes notas de proposta de recurso: