Uma expressão lambda é uma pequena função anônima escrita no meio do código com o operador => ("vai para"). Os parâmetros ficam à esquerda, o resultado à direita:
Saída:
60.0
12
ORDER SHIPPED!
96.0, 180.0
Uma lambda não tem tipo próprio. Ela vira um delegate (um objeto que aponta para uma função) do tipo delegate que o contexto espera: Func<decimal, decimal> aqui, um Predicate<T> para List.FindAll, um EventHandler para um evento. Delegates Func retornam um valor e delegates Action não; a página de Func e Action lista a família completa.
Formas de sintaxe
x => x * x // one parameter: parentheses optional
(x, y) => x + y // several parameters
() => DateTime.Now.Year // no parameters
(int x, string s) => s.Length > x // explicit parameter types
order => // statement lambda: a block with return
{
decimal subtotal = order.Qty * order.Price;
return subtotal > 100m ? subtotal * 0.9m : subtotal;
}
Uma lambda de expressão tem uma única expressão depois do =>, e o valor dela é o resultado. Uma lambda de instrução tem um bloco entre chaves, que pode conter quaisquer instruções e precisa fazer return de um valor em todos os caminhos se o delegate retornar um (senão é CS1643, Not all code paths return a value in lambda expression).
Versões mais novas adicionaram outras formas, todas opcionais:
- C# 9: parâmetros de descarte, então vários parâmetros não usados podem ser todos
_((_, _) => Save()), e lambdasstatic, proibidas de capturar variáveis. - C# 10: um "tipo natural", então
var square = (int x) => x * x;infereFunc<int, int>. Antes disso,var f = x => x * 2;falha; mesmo no C# 10 ainda falha sem o tipo do parâmetro, com CS8917The delegate type could not be inferred. - C# 12: valores padrão de parâmetro,
(int x, int y = 1) => x + y.
Passando lambdas para métodos
Qualquer método que recebe um parâmetro delegate aceita uma lambda. É assim que você passa comportamento, não só dados:
Saída:
report.pdf, scan.pdf
photo.jpg, cv.txt, scan.pdf
cv.txt, scan.pdf, photo.jpg, report.pdf
Filter não sabe o que "manter" significa; quem chama decide com uma lambda. List.Sort funciona do mesmo jeito: a lambda é uma comparação que retorna negativo, zero ou positivo, e Sort a chama quantas vezes precisar.
Lambdas no LINQ
O LINQ é onde a maior parte do código C# encontra lambdas. Todo operador de consulta recebe uma: Where uma condição, Select uma projeção, OrderBy uma chave, Sum um valor a somar:
Saída:
Ben: 250
Ana: 60
Cy: 45
Ana spent 80
Ben spent 250
Cy spent 45
As lambdas não executam onde são escritas. O LINQ as guarda e as chama quando a consulta é enumerada por foreach, ToList() ou Sum(), o que se chama execução adiada. A página de LINQ cobre os operadores em si.
Closures: variáveis capturadas
Uma lambda pode usar variáveis locais e parâmetros do método em que é escrita. Ela captura a variável, não o valor atual, e a variável continua viva enquanto a lambda existir:
Saída:
1
2
3
1
50.0
Cada chamada a MakeCounter cria um count novo, então os dois contadores são independentes. A lambda de desconto leu rate quando executou, não quando foi escrita, por isso aplicou 50%. O compilador implementa isso movendo as variáveis capturadas para uma classe oculta que o método e a lambda compartilham.
A armadilha da captura no laço
Capturar uma variável de laço é onde as closures mordem. Um laço for tem uma variável para o laço inteiro, então toda lambda criada nele compartilha essa variável:
Saída:
3 3 3 <- for loop, shared i
0 1 2 <- for loop, copied
0 1 2 <- foreach
As três lambdas do primeiro laço executaram depois que o laço terminou, quando i valia 3. Copiar i para uma variável declarada dentro do corpo dá a cada lambda a sua própria. O foreach acerta isso sozinho: desde o C# 5, a variável de iteração dele é uma variável nova a cada passada. Antes do C# 5 ele tinha o mesmo bug, e é por isso que respostas antigas na internet ainda recomendam a cópia também para o foreach.
A mesma armadilha vale para tudo o que executa depois: handlers de eventos, timers, Task.Run e consultas LINQ montadas em um laço e enumeradas depois dele.
Lambdas, métodos anônimos e funções locais
As lambdas substituíram a sintaxe de método anônimo do C# 2, que ainda compila:
Func<int, bool> isEven = delegate (int n) { return n % 2 == 0; }; // C# 2 anonymous method
Func<int, bool> isEven2 = n => n % 2 == 0; // lambda
Para um auxiliar usado só dentro de um método, o C# 7.0 também oferece funções locais, que têm nome, podem ser recursivas e não alocam um delegate a menos que você as converta em um. Use uma lambda quando você passa comportamento para algo (LINQ, Sort, um evento); use uma função local ou um método privado quando você mesmo vai chamá-lo.
Mais uma distinção aparece com provedores LINQ como o Entity Framework: uma lambda atribuída a Expression<Func<T, bool>> não é compilada para código, e sim para uma estrutura de dados que descreve o código, que o provedor traduz para SQL. A sintaxe é idêntica; o tipo do parâmetro do método que você chama decide qual das duas você recebe.
Perguntas frequentes
O que é uma expressão lambda em C#?
Uma função anônima escrita com o operador =>: x => x * 2 recebe x e retorna x * 2. Uma lambda não tem nome próprio; ela é convertida para um tipo delegate como Func<int, int> ou Action<string> e pode ser guardada em uma variável, passada para um método ou retornada por ele.
Como escrever uma lambda com vários parâmetros ou sem parâmetros?
Coloque os parâmetros entre parênteses: (a, b) => a + b para dois, () => DateTime.Now para nenhum. Só um parâmetro único pode dispensar os parênteses (x => x + 1). Você também pode informar os tipos explicitamente: (int a, int b) => a + b.
O que é uma closure em uma lambda em C#?
Uma lambda que usa uma variável local do método em volta captura essa variável, não uma cópia do valor. A variável vive enquanto a lambda viver, e se um dos lados mudá-la depois, o outro vê a mudança. É isso que faz contadores e callbacks funcionarem, e é o que causa o clássico bug de captura no laço for.
Por que as lambdas em um laço for veem todas o mesmo valor?
Um laço for tem uma única variável de controle para o laço inteiro, então toda lambda criada dentro dele captura o mesmo i e vê o valor final quando executa depois. Copie-o para uma variável local dentro do corpo (int copy = i;) e capture a cópia. O foreach não tem esse problema desde o C# 5, porque cria uma variável nova a cada iteração.
Por que var f = x => x * 2 não compila?
O compilador não consegue saber qual tipo delegate você quer nem qual é o tipo de x, então reporta CS8917 The delegate type could not be inferred (antes do C# 10, CS0815). Declare o tipo (Func<int, int> f = x => x * 2;) ou, no C# 10 em diante, tipe o parâmetro: var f = (int x) => x * 2;.