1. Delegates — um "tipo" para funções
Em JavaScript, uma função é um valor de primeira classe — você já passa funções como argumento sem pensar muito nisso. Em C#, para guardar uma referência a um método numa variável, você precisa de um delegate: um tipo que descreve a assinatura (parâmetros e retorno) que a função precisa ter.
public delegate decimal OperacaoMatematica(decimal a, decimal b);
decimal Somar(decimal a, decimal b) => a + b;
decimal Multiplicar(decimal a, decimal b) => a * b;
OperacaoMatematica operacao = Somar;
Console.WriteLine(operacao(2, 3)); // 5
operacao = Multiplicar;
Console.WriteLine(operacao(2, 3)); // 6
2. Func, Action e Predicate — delegates prontos
Na prática, você raramente declara um delegate customizado — o .NET
já traz três genéricos que cobrem quase todos os casos:
| Delegate | Assinatura | Equivalente JS |
|---|---|---|
Action | Sem parâmetros, sem retorno | () => { ... } |
Action<T> | Um parâmetro, sem retorno | (x) => { ... } |
Func<T, TResult> | Um parâmetro, com retorno | (x) => valor |
Predicate<T> | Um parâmetro, retorna bool | (x) => x > 0 |
Func<decimal, decimal, decimal> somar = (a, b) => a + b;
Console.WriteLine(somar(2, 3)); // 5
Action<string> logar = mensagem => Console.WriteLine($"[LOG] {mensagem}");
logar("Aplicação iniciada");
// exatamente o que .Where() do LINQ (capítulo 9) recebe por trás dos panos:
Func<Cliente, bool> filtro = c => c.Ativo;
var ativos = clientes.Where(filtro).ToList();
Toda vez que você escreveu .Where(c => c.Ativo) ou
.Select(c => c.Nome) no capítulo de LINQ, estava passando uma
expressão lambda como um Func<T, TResult>. Agora você sabe o
que estava acontecendo por trás da sintaxe.
3. Expressões lambda em detalhe
Func<int, int> dobro = x => x * 2;
Func<int, int, int> soma = (x, y) => x + y;
Func<int, bool> ehPar = (int x) => x % 2 == 0; // tipo explícito, opcional
Action semParametros = () => Console.WriteLine("oi");
Func<int, int> comBloco = x =>
{
var resultado = x * 2;
return resultado; // corpo de bloco precisa de return explícito
};
É a mesma flexibilidade das arrow functions do JS — parâmetro único sem parênteses, múltiplos parâmetros com parênteses, corpo de expressão vs. corpo de bloco.
4. Eventos — delegates com um padrão de publicação/assinatura
Um evento é um delegate especial que só a própria classe pode disparar
(invoke), mas qualquer código externo pode "assinar" (adicionar um
método para ser chamado quando o evento disparar). É o equivalente conceitual de
EventEmitter/addEventListener do mundo
JS/Node.
public class Pedido
{
public event Action<Pedido>? StatusAlterado;
private string _status = "Pendente";
public string Status
{
get => _status;
set
{
_status = value;
StatusAlterado?.Invoke(this); // dispara o evento, se alguém assinou
}
}
}
var pedido = new Pedido();
// "assinando" o evento — igual a addEventListener
pedido.StatusAlterado += p => Console.WriteLine($"Pedido mudou para: {p.Status}");
pedido.StatusAlterado += p => EnviarEmailNotificacao(p);
pedido.Status = "Confirmado"; // dispara os dois assinantes acima
| C# | JavaScript/Node |
|---|---|
event Action<T> Evento; | declarar um evento num EventEmitter |
objeto.Evento += handler; | emitter.on('evento', handler) |
objeto.Evento -= handler; | emitter.off('evento', handler) |
Evento?.Invoke(...) | emitter.emit('evento', ...) |
Sempre use ?.Invoke(...) (com o operador null-conditional do
capítulo 2), não Evento.Invoke(...) direto — se ninguém assinou o
evento, ele é null, e chamar .Invoke
num delegate nulo lança NullReferenceException.
5. Onde isso te leva
Delegates e eventos são a base de muita coisa que vem depois — callbacks assíncronos, e principalmente os padrões de projeto do próximo capítulo, vários dos quais usam exatamente essa ideia de "passar comportamento como dado" para desacoplar peças do sistema.