1. Introdução
Imagine que você tem uma máquina de café. Normalmente ela só faz café e não incomoda ninguém, mas às vezes o chefe diz: “Você pode, por favor, depois de fazer o café gritar ‘Pronto!’?” — aí precisa de uma configuração. Não reescrevemos a máquina de café. Só criamos uma função que ela vai executar no final.
Em C# esse papel é feito pelos delegates — eles permitem passar para métodos pedaços de código (métodos, lambdas ou métodos anônimos), para que esse método os invoque no momento certo. Resumindo, um delegate é um tipo que pode guardar referências para métodos com uma assinatura específica.
Definição de delegate
Em C# um delegate é definido com a palavra-chave delegate. Exemplo:
// Delegate que referencia métodos que aceitam int e retornam bool
public delegate bool PredicateInt(int x);
Agora uma variável do tipo PredicateInt pode referenciar qualquer método (ou lambda!) que aceite um int e retorne um bool.
Para que servem os delegates?
- Passar lógica como argumento (por exemplo, para sort, filter, manipulação de eventos)
- Assinatura para eventos (sobre isso falamos depois)
- Implementar callbacks
- APIs flexíveis, onde parte do comportamento é definida pelo chamador
Esquema visual simples
| Tipo de delegate | Assinatura | Exemplo de chamada |
|---|---|---|
|
|
|
|
|
|
|
|
|
2. Como a lambda vira um delegate
Sintaxe
Quando você escreve uma expressão lambda, por exemplo, x => x > 5, você na prática está criando um objeto delegate. A lambda não “vive” no vácuo: ela precisa de um tipo (alguém tem que saber quais são seus parâmetros e o valor retornado). Por isso uma expressão lambda em C# é sempre convertida, implicitamente (ou explicitamente), para um delegate.
Exemplo 1: Vinculando um delegate a um método
// Definimos explicitamente um delegate
public delegate bool MyPredicate(int number);
class Program
{
static void Main()
{
// Atribuímos uma lambda a uma variável do tipo MyPredicate
MyPredicate isEven = x => x % 2 == 0;
Console.WriteLine(isEven(4)); // true
Console.WriteLine(isEven(7)); // false
}
}
Exemplo 2: Usando delegates padrão
C# tem um conjunto de delegates genéricos padrão: Action, Func<>, Predicate<>. Eles aparecem quase sempre quando você escreve lambdas no código.
// Usando Func
Func
isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false
3. Delegates padrão: Func, Action, Predicate
Func<...>
Usado para métodos que recebem algo e retornam algo.
Assinatura:
— O último tipo é o valor retornado, os anteriores são os tipos dos parâmetros, por exemplo:
Func<int, string> — recebe int, retorna string
Func
intToString = number => "Número: " + number; Console.WriteLine(intToString(7)); // "Número: 7"
Action<...>
Usado quando você precisa executar algo, mas não retornar nada (void).
Action
printHello = name => Console.WriteLine("Olá, " + name + "!"); printHello("Vasili"); // "Olá, Vasili!"
Predicate<T>
Na prática, um atalho para Func<T, bool>. Usado quando precisa de uma verificação booleana sobre um objeto (true/false).
Predicate
isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true
Resumo visual rápido
| Delegate | Assinatura | Aplicação |
|---|---|---|
|
|
Transformação, projeção |
|
|
Efeitos colaterais, saída |
|
|
Filtragem, busca |
Qual tipo de delegate escolher para uma lambda?
- Se for esperado um valor de retorno, use Func<...>
- Se o método não retorna nada (void), use Action<...>
- Se precisa de uma verificação condicional, use Predicate<T>
Exemplo: Filtrando uma lista
List
numbers = new List
{ 1, 2, 3, 4, 5, 6 }; // Espera Predicate
List
evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6
4. Dicas úteis
Expressões lambda e métodos de coleção: o que rola por baixo?
Quando você chama um método de coleção passando uma lambda, por exemplo:
var adults = users.Where(u => u.Age >= 18);
O método Where espera um argumento do tipo Func<T, bool>. Ou seja, sua lambda u => u.Age >= 18 é convertida pelo compilador em um objeto delegate desse tipo.
Fluxo: como isso funciona
Sua lambda --> Compilador C# --> Objeto delegate (Func
) (u => u.Age >= 18) [tipo conhecido] (Pronto pra chamar em Where())
Mais sobre tipagem: type inference
Geralmente o tipo do delegate é inferido automaticamente pelo compilador quando está claro pelo contexto. Por exemplo, para o método List<T>.Find é esperado um Predicate<T>, e o compilador conhece o tipo do parâmetro pela assinatura do método.
List
words = new List
{ "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find espera Predicate
Console.WriteLine(result); // "three"
Se o contexto não for claro, você precisa ajudar o compilador:
// Especificando o tipo explicitamente
Func
check = x => x > 10;
Delegates retornados: fábricas de funções
Às vezes métodos podem retornar delegates — ou seja, criar “fábricas de funções”. Isso é útil para gerar comportamento dinâmico.
// Função que retorna um delegate (lambda)
Func
GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15
Isso funciona porque a lambda (x => x * factor) captura a variável factor do contexto externo (closure/fechamento) e é retornada como um objeto do tipo Func<int, int>.
5. Erros e mal-entendidos com delegates e lambdas
Assinaturas incompatíveis
O compilador não deixa atribuir uma lambda a um delegate se os parâmetros ou o valor de retorno não coincidirem.
Func
f = x => "Não dá pra retornar uma string!"; // Erro de compilação
Erro ao tentar usar uma lambda sem delegate
Não dá para simplesmente escrever uma lambda e tentar chamá-la sem tipo:
// Isso não vai funcionar - o compilador não consegue inferir o tipo
// var myFunc = x => x * 2; // Erro CS0815
// myFunc(10);
Para funcionar, você precisa especificar o tipo, ou fornecer contexto:
Func
myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20
Confusão entre Action, Func e Predicate
Às vezes você escolhe o tipo errado de delegate e daí dá erro de compatibilidade de assinatura. Lembre-se da regra simples: Func — quando há um resultado, Action — quando não há resultado (void), Predicate<T> — quando precisa de uma resposta booleana (bool).
GO TO FULL VERSION