Módulo 1 · C# — Capítulo 11

Delegates, Eventos e Lambda

Como C# trata funções como valores — algo que você já faz naturalmente em JavaScript — e o mecanismo de eventos construído em cima disso.

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.

C# delegate customizado
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:

DelegateAssinaturaEquivalente JS
ActionSem 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
C# Func e Action em uso
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();
Conectando com o capítulo anterior

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

C# variações de sintaxe lambda
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.

C# publicando e assinando um evento
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', ...)
Atenção

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.

📌 Resumo do capítulo

  • Delegate é um tipo que descreve a assinatura de um método, permitindo tratá-lo como valor.
  • Func<T,TResult>, Action<T> e Predicate<T> cobrem a maioria dos casos sem precisar declarar delegates customizados.
  • Toda expressão lambda passada para LINQ é, por baixo dos panos, um desses delegates.
  • event é um delegate com padrão de publicação/assinatura, equivalente ao EventEmitter do Node — use sempre ?.Invoke() para disparar com segurança.

✏️ Praticando

  1. Escreva um Func<decimal, decimal, decimal> para cada operação básica (soma, subtração, multiplicação, divisão) e um método que recebe a operação como parâmetro e a aplica a dois valores.
  2. Adicione um evento EstoqueBaixo na classe Produto (capítulo 5), disparado quando o estoque cair abaixo de 10 unidades, e assine-o com um método que imprime um alerta.