Capítulo 13: Delegates, Eventos Fortemente Tipados e Lambdas
Capítulo 13: Delegates, Eventos Fortemente Tipados e Lambdas
Especialização em Sistemas com C# • C# 12 & .NET 8 • POO Moderna, LINQ, Concorrência e Arquitetura Corporativa
🗺️ Mapa Conceitual de Delegates, Invocação Multicast e Eventos
flowchart TD
Origem["Invocação de Funções de Primeira Classe"] --> Delegate["System.MulticastDelegate (Ponteiro Tipado + Objeto-Alvo)"]
subgraph DELEGATE_FAMILIES["Delegates Padronizados da BCL"]
Action["Action<Ts...>: Métodos com Retorno void"]
Func["Func<Ts..., TResult>: Métodos com Retorno Tipado"]
Predicate["Predicate<T>: Validações Retornando bool"]
end
subgraph EVENTS_PUBSUB["Padrão Event (Publisher / Subscriber)"]
EventHandler["EventHandler<TEventArgs>: Contrato Idiomático"]
AddRemove["Acessores add / remove (Controle Estrito de Assinatura)"]
WeakEvents["WeakEventManager (Prevenção de Memory Leaks)"]
end
Delegate --> DELEGATE_FAMILIES
Delegate --> EVENTS_PUBSUB
EVENTS_PUBSUB --> InvocationList["Lista Encadeada de Invocação Multicast (_invocationList)"]
style Origem fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style DELEGATE_FAMILIES fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style EVENTS_PUBSUB fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style InvocationList fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Que É um Delegate Sob a Perspectiva da CLR
No ecossistema .NET, um Delegate não é um simples ponteiro de função bruto do C (void(*)()). Ele é uma classe gerenciada completa sintetizada pelo compilador que herda de System.MulticastDelegate:
Estrutura Interna de um Delegate em Memória:
_target(Object): Referência ao objeto de instância sobre o qual o método será executado (ounullse o método for estático)._methodPtr(IntPtr): Ponteiro de função nativo do código de máquina do método alvo._invocationList(Object[]): Quando múltiplos métodos são encadeados via operador+=, o delegate armazena uma lista de outros delegates, disparando-os sequencialmente durante oInvoke().
⚙️ 2. A Trindade de Delegates Modernos: Action, Func e Predicate
Desde o advento dos tipos genéricos, a necessidade de declarar delegates customizados (delegate void MeuHandler()) foi praticamente eliminada. A biblioteca padrão fornece uma família unificada:
Action<T1, T2, ...>: Representa um método que recebe de 0 a 16 argumentos e retornavoid.Func<T1, T2, ..., TResult>: Representa um método que recebe de 0 a 16 argumentos e retorna obrigatoriamente um valor do tipoTResult(o último tipo genérico especificado).Predicate<T>: Especialização equivalente aFunc<T, bool>, comumente utilizada para filtros em coleções (list.FindAll(x => x.Ativo)).
📦 3. Eventos e o Padrão de Assinatura Desacoplada
A palavra-chave event atua como um modificador de encapsulamento sobre um delegate. Ela restringe o acesso externo:
- Classes externas podem apenas adicionar assinantes (
+=) ou remover assinantes (-=). - Apenas a classe proprietária que declarou o evento tem permissão para invocar (
Invoke) ou limpar a lista de inscritos.
O Padrão Canônico EventHandler<TEventArgs>:
public class AlertaTelemetriaEventArgs(string sensorId, double temperatura) : EventArgs
{
public string SensorId { get; } = sensorId;
public double Temperatura { get; } = temperatura;
}
public class CentralMonitoramento
{
// Declaração segura do evento
public event EventHandler<AlertaTelemetriaEventArgs>? LimiteExcedido;
protected virtual void DispararLimiteExcedido(string sensorId, double temp)
{
// Invocação thread-safe: cópia atômica da referência do delegate
LimiteExcedido?.Invoke(this, new AlertaTelemetriaEventArgs(sensorId, temp));
}
}
📊 4. Matriz Comparativa: Delegate vs Evento vs Interface
| Critério | Delegate Simples | Evento (event) |
Interface |
|---|---|---|---|
| Invocação Externa | Permitida por qualquer código | Proibida (Apenas dono invoca) | N/A (chamada de método direto) |
Atribuição Direta (=) |
Permitida (pode sobrescrever outros) | Proibida (Apenas += e -=) |
N/A |
| Acoplamento | Mínimo (apenas assinatura) | Mínimo (Publisher / Subscriber) | Médio (acoplamento ao contrato) |
| Múltiplos Destinos? | Sim (Multicast) | Sim (Multicast) | Depende de implementação manual |
🔬 5. Laboratório de Arquitetura: O Risco de Memory Leaks em Eventos e Weak Events
Uma das fontes mais insidiosas de vazamento de memória em aplicações .NET de longa duração é a inscrição pendente de eventos:
- Quando um objeto assinante de curta vida inscreve um método em um publicador de longa vida (como um serviço singleton
CentralMonitoramento.Alerta += assinante.TratarAlerta), o publicador mantém uma referência forte paraassinante._targetem sua_invocationList. - O Garbage Collector nunca conseguirá coletar o assinante, mesmo que ele não seja mais utilizado pelo resto da aplicação!
A Solução: Inscrição com Desinscrição Rigorosa ou WeakEventManager
- Sempre implemente
IDisposablenos assinantes para remover o evento no momento da destruição (evento -= handler;). - Utilize referências fracas através da classe
WeakEventManagerou tokens de assinatura descartáveis (Subscription Tokens).