C#: Expressões lambda
1. Visão geral das expressões lambda
Uma expressão Lambda é uma sintaxe concisa de função anônima que permite definir funções inline com menos código. É um recurso essencial da programação funcional em C# e a base das consultas LINQ.
2. Sintaxe de Lambda
A sintaxe básica de uma expressão Lambda é: parameters => expression or statement block. => é lido como “vai para”, mapeando os parâmetros do lado esquerdo para a operação do lado direito.
▶ Exemplo
using System;
class Program
{
static void Main()
{
Func`<int, int>` square = x => x * x;
Console.WriteLine(square(5));
}
}
25
3. Lambdas de uma linha e de várias linhas
Uma expressão Lambda de uma única linha retorna diretamente o resultado da expressão, sem a palavra-chave return nem chaves. Uma expressão Lambda de várias linhas requer chaves para delimitar o bloco de instruções e uma instrução explícita return.
▶ Exemplo
using System;
class Program
{
static void Main()
{
Func`<int, int>` singleLine = x => x * x;
Func`<int, int>` multiLine = x =>
{
int result = x * x;
return result;
};
Console.WriteLine(singleLine(4));
Console.WriteLine(multiLine(4));
}
}
16
16
4. Inferência de tipos
Normalmente, o compilador consegue inferir os tipos dos parâmetros Lambda a partir do contexto, eliminando a necessidade de declarações explícitas. Quando o contexto é ambíguo, é possível especificar os tipos manualmente.
▶ Exemplo
using System;
class Program
{
static void Main()
{
Func`<int, int>` inferred = x => x + 1;
Func`<int, int>` explicitType = (int x) => x + 1;
Console.WriteLine(inferred(10));
Console.WriteLine(explicitType(10));
}
}
11
11
5. Lambdas e delegados
As expressões lambda podem ser atribuídas a tipos de delegado, o que as torna a maneira mais concisa de criar instâncias de delegado. Em comparação com os métodos anônimos tradicionais ou com a instanciação explícita de delegados, as expressões lambda são mais curtas.
▶ Exemplo
using System;
delegate int Transform(int x);
class Program
{
static void Main()
{
Transform doubleIt = x => x * 2;
Transform addTen = x => x + 10;
Console.WriteLine(doubleIt(3));
Console.WriteLine(addTen(3));
}
}
6
13
6. Lambdas com Func e Action
Func<T, TResult> representa um delegado genérico com valor de retorno, e Action<T> representa um delegado genérico sem valor de retorno. É muito comum usar expressões lambda com eles.
▶ Exemplo
using System;
class Program
{
static void Main()
{
Func`<int, int, int>` add = (a, b) => a + b;
Action```<string>``` greet = msg => Console.WriteLine(msg);
Console.WriteLine(add(3, 7));
greet("Hello");
}
}
10
Hello
7. Lambdas e predicados
Predicate<T> é um delegado que retorna bool, comumente usado para filtragem e verificações condicionais. As expressões lambda tornam a definição de condições extremamente concisa.
▶ Exemplo
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var numbers = new List```<int>``` { 1, 2, 3, 4, 5, 6 };
Predicate```<int>``` isEven = x => x % 2 == 0;
var evens = numbers.FindAll(isEven);
Console.WriteLine(string.Join(", ", evens));
}
}
2, 4, 6
8. Fechamentos
As expressões lambda podem capturar variáveis externas, o que é chamado de closure. O tempo de vida das variáveis capturadas se estende junto com a expressão lambda — mesmo após o término do escopo original, as variáveis permanecem ativas.
▶ Exemplo
using System;
class Program
{
static void Main()
{
int factor = 3;
Func`<int, int>` multiply = x => x * factor;
Console.WriteLine(multiply(5));
factor = 10;
Console.WriteLine(multiply(5));
}
}
15
50
▶ Exemplo
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var actions = new List`<Action>`();
for (int i = 0; i < 3; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
foreach (var action in actions)
{
action();
}
}
}
0
1
2
i sem criar uma cópia, todas as funções lambda compartilharão a mesma i, e o resultado final será 3 em todos os casos. Criar uma cópia local evita esse problema.
9. Lambda no LINQ — Uma introdução
As expressões lambda são fundamentais para as consultas LINQ. Muitos métodos LINQ aceitam parâmetros Func ou Expression, e as expressões lambda oferecem uma maneira concisa de escrever condições, projeções e lógica de classificação diretamente no código.
▶ Exemplo
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var numbers = new List```<int>``` { 3, 7, 1, 9, 4, 6 };
var big = numbers.Where(x => x > 5);
var doubled = numbers.Select(x => x * 2);
var sorted = numbers.OrderBy(x => x);
Console.WriteLine(string.Join(", ", big));
Console.WriteLine(string.Join(", ", doubled));
Console.WriteLine(string.Join(", ", sorted));
}
}
7, 9, 6
6, 14, 2, 18, 8, 12
1, 3, 4, 6, 7, 9
10. Introdução às árvores de expressão
Quando uma função Lambda é atribuída a Expression<TDelegate>, o compilador gera uma estrutura de dados (árvore de expressões) em vez de código executável. As árvores de expressões podem ser analisadas, modificadas e transformadas em outras formas, como consultas SQL. Esse é o mecanismo subjacente a tecnologias como o LINQ to SQL.
▶ Exemplo
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func`<int, int>`> expr = x => x * x + 1;
Console.WriteLine(expr.Body);
Console.WriteLine(expr.Parameters[0]);
var compiled = expr.Compile();
Console.WriteLine(compiled(4));
}
}
(x * x) + 1
x
17
ℹ️
Expression<Func``<int,int>``>eFunc<int,int>são tipos diferentes — o primeiro é uma estrutura de dados, enquanto o último é um delegado executável.
❓ Perguntas Frequentes
P: Qual é a diferença entre lambda e métodos anônimos? R: A sintaxe do lambda é mais concisa, suporta inferência de tipos e árvores de expressão; a sintaxe do método anônimo é mais prolixa e possui menos recursos. P: Quando os tipos dos parâmetros Lambda devem ser especificados explicitamente? R: Quando o compilador não consegue inferir o tipo a partir do contexto, como no caso de tipos de parâmetros ambíguos ou conflitos na resolução de sobrecargas. P: Quando as variáveis capturadas por closures são liberadas? R: As variáveis capturadas só são liberadas depois que todas as lambdas que as referenciam tiverem sido recolhidas pelo garbage collector. P: É possível converter Expression e Func um no outro? R: É possível converter Expression em Func por meio do método Compile(), mas não é possível converter Func diretamente de volta em Expression.
📖 Resumo
- A sintaxe Lambda é
parameters => expression, que define de forma concisa funções anônimas - As lambdas de uma linha omitem as chaves e o
return; as lambdas de várias linhas exigem ambos - O compilador pode inferir os tipos dos parâmetros das funções lambda; é possível fazer uma declaração explícita quando necessário
- As funções lambda podem ser atribuídas diretamente a delegados personalizados, como Func, Action e Predicate
- Os closures permitem que as funções lambda capturem variáveis externas, prolongando seu tempo de vida
- Ao capturar variáveis de loop, crie cópias para evitar o compartilhamento indesejado
- As lambdas são a principal forma de definição de parâmetros para os métodos do LINQ
- As árvores de expressão representam as lambda como estruturas de dados, permitindo a análise e a transformação em tempo de execução
📝 Exercícios
- Escreva uma função Lambda
Func<double, double, double>que calcule o máximo entre dois números - Use um Lambda
Action<string>para gerar uma mensagem de log com um carimbo de data e hora - Escreva um exemplo de closure: crie uma função contadora que retorne um número inteiro incrementado a cada vez que for chamada
- Use
Wherecom uma função Lambda para filtrar strings com mais de 5 caracteres de uma lista de strings - Atribua uma função Lambda
Func<int, bool>tanto aFunc<int, bool>quanto aExpression<Func``<int, bool>``>e exiba seus resultadosGetType()respectivamente