CodeGym /Cursos /C# SELF /Análise detalhada da assinatura (

Análise detalhada da assinatura ( +=)

C# SELF
Nível 53, Lição 0
Disponível

1. Introdução

Eventos em C# não são só variáveis onde você guarda um delegate. São uma lista protegida de handlers, e só o dono do evento pode dispará-lo; os outros só podem adicionar (+=) ou remover (-=) suas reações.

Por exemplo, assim você pode se inscrever num evento:

worker.WorkCompleted += Worker_WorkCompleted;

À primeira vista parece uma soma comum, mas na prática funciona diferente. Por baixo do capô o evento guarda uma invocation list — um conjunto de delegates que precisam ser chamados quando o evento dispara. Quando você escreve +=, um novo handler é adicionado a essa lista.

Vamos entender em mais detalhe o que acontece internamente, como essa cadeia é formada e que nuances podem surgir ao assinar.

O que é uma cadeia de delegates?

Lembra que delegates em C# são "multicast": você pode associar vários métodos e todos serão executados na ordem se o delegate for invocado. Eventos usam esse mecanismo: o valor deles é, na prática, um delegate com uma lista de handlers.

Em termos de código:

public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

Quando alguém se inscreve:

worker.WorkCompleted += MyHandler;

O C# por baixo faz mais ou menos assim:

  • Pega o delegate atual (a lista de handlers).
  • Chama Delegate.Combine para unir os handlers.
  • Grava a cadeia atualizada de volta na variável do evento.

Esquematicamente:

Operação Lista interna de handlers do evento
Antes null ou [Handler1]
Depois de += Handler2 [Handler1, Handler2]
Depois de mais um += H3 [Handler1, Handler2, Handler3]

Curiosidade: o mecanismo de multicast delegates não é "mágica", é uma implementação concreta: por baixo o delegate contém um array de métodos a serem chamados.

2. Como funciona a assinatura: explicação simples

Vamos ver com um exemplo concreto. Suponha que temos um publisher (Worker) e subscribers (Logger, Notifier):

public class Worker
{
    public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

    public void DoWork()
    {
        // ... trabalho ...
        OnWorkCompleted("Zadanie zaversheno!");
    }

    protected virtual void OnWorkCompleted(string message)
    {
        WorkCompleted?.Invoke(this, new WorkCompletedEventArgs { Message = message });
    }
}

public class Logger
{
    public void LogWorkCompleted(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Log: " + e.Message);
    }
}

public class Notifier
{
    public void ShowNotification(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Uvedomlenie: " + e.Message);
    }
}

No Main:

var worker = new Worker();
var logger = new Logger();
var notifier = new Notifier();

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += notifier.ShowNotification;

// Inicia o trabalho
worker.DoWork();

Quando OnWorkCompleted é chamado, o evento primeiro invoca logger.LogWorkCompleted, depois notifier.ShowNotification (na ordem em que você se inscreveu).

3. Nuances úteis

Visualização: como o evento guarda os handlers

+---------------------+
| Worker              |
|---------------------|
| WorkCompleted Event |    ---> [ LogWorkCompleted, ShowNotification ]
+---------------------+

Ao assinar, cada novo handler "se prende" à lista existente. Depois do disparo, o delegate chama sequencialmente todos os métodos inscritos.

Assinatura múltipla com o mesmo método

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += logger.LogWorkCompleted; // Duas vezes!

Nesse caso o handler será chamado tantas vezes quantas estiver inscrito — aqui serão duas chamadas seguidas.

Assinar com uma lambda

worker.WorkCompleted += (sender, e) => Console.WriteLine("Anonymnyi handler: " + e.Message);

Se você inscrever essa lambda várias vezes — vai ser chamada várias vezes também. Mas atenção: cada lambda cria um objeto delegate distinto, então não é trivial remover depois (veja a aula 260 em diante para detalhes).

Como a assinatura funciona internamente: análise de baixo nível

Evento é uma propriedade especial com dois acessores (add/remove) que são chamados quando se usa += e -=. Simplificando, o compilador gera algo como:

// Mais ou menos assim (simplificado)
public event EventHandler<WorkCompletedEventArgs> WorkCompleted
{
    add { /* código de adição do handler */ }
    remove { /* código de remoção do handler */ }
}

Por padrão a implementação usa Delegate.Combine e Delegate.Remove.

Isso protege o evento: externamente você não pode invocar o evento diretamente (nada de worker.WorkCompleted(...);), só pode assinar ou desassinar.

Dinâmica da assinatura: desenhando o esquema

             +----------------------+
             |                      |
             v                      v
    +--------------------+   +----------------------+
    | LogWorkCompleted   |   | ShowNotification    |
    +--------------------+   +----------------------+
             ^                      ^
             \______________________/
                       ^
                       |
               WorkCompleted Event

A tal "Invocation List" — a cadeia de chamadas.

Por que isso importa: significado prático

Entender a mecânica de assinatura é chave pra gerenciar dependências entre objetos. Você pode montar sistemas onde componentes se inscrevem e desinscrevem dinamicamente sem criar acoplamento forte. Isso é padrão em frameworks UI, engines de jogos, servidores e até em arquiteturas modernas de microservices (lá a ideia fica na camada de filas, mas o princípio é parecido).

Em entrevistas costumam perguntar como o modelo de eventos do C# funciona, por que eventos deixam o sistema flexível e como gerenciar lifecycle de assinaturas corretamente.

4. O que se pode (e não se pode) fazer de fora da classe

Pode

  • Assinar o evento (+=)
  • Desassinar (-=)

Não pode

  • Invocar o evento diretamente
  • Atribuir um delegate ao evento diretamente (worker.WorkCompleted = ... — erro!)

Essas restrições são implementadas pela palavra-chave event. Se você declarasse o delegate como um campo comum:

public EventHandler<WorkCompletedEventArgs> WorkCompleted; // ne event!

— qualquer um poderia fazer de tudo com ele, até zerar os handlers, o que provocaria bagunça e bugs potenciais. Por isso quase sempre use somente event!

É possível assinar o mesmo método em vários eventos?

Sim! Isso se chama "multisubscribe". Por exemplo:

worker.WorkCompleted += logger.LogWorkCompleted;
anotherWorker.WorkCompleted += logger.LogWorkCompleted;

Se você assinou o mesmo método em eventos de objetos diferentes, o handler será executado para ambos. Dentro do handler dá pra distinguir quem disparou pelo parâmetro sender.

Caso real: assinaturas dinâmicas

Imagina um app onde o usuário roda várias tarefas em paralelo. Pra cada tarefa se cria um Worker e assina o mesmo handler — por exemplo, logger.LogWorkCompleted.

var allWorkers = new List<Worker>();
for (int i = 0; i < 10; i++)
{
    var w = new Worker();
    w.WorkCompleted += logger.LogWorkCompleted;
    allWorkers.Add(w);
}

Assim, quando qualquer tarefa terminar, o logger recebe a notificação e registra o que rolou e quando.

Prática: como ver os subscribers de um evento

No código comum não dá pra saber diretamente quantos handlers estão inscritos (o evento encapsula o delegate). Mas dentro da classe publisher você pode acessar o delegate e usar GetInvocationList():

// Só dentro da classe publisher
var handlers = WorkCompleted?.GetInvocationList();
if (handlers != null)
    Console.WriteLine($"Podpischikov: {handlers.Length}");

Isso é útil se você precisa fazer lógica custom de broadcast ou debug (embora seja melhor usar só para fins educativos!).

5. Erros típicos e particularidades

Handler não foi adicionado? Não será chamado!
Se você não se inscreveu, o handler nunca vai rodar. Antes de disparar o evento sempre verifique null (senão rola NullReferenceException).

Assinar várias vezes
Se você assinou múltiplas vezes o mesmo método no mesmo evento — ele será chamado múltiplas vezes. Às vezes isso pega a galera desprevenida: um += duplicado transforma uma notificação em várias idênticas.

Métodos estáticos/instância
Você pode assinar tanto métodos static quanto de instância. O importante é a assinatura bater com a delegate signature.

public static void StaticHandler(object? sender, WorkCompletedEventArgs e) { /* ... */ }
worker.WorkCompleted += StaticHandler;

Lambdas e assinatura dentro de loop
Se você, por exemplo, em um loop assina lambdas, cuide do que a lambda captura. Dá pra acabar capturando um valor diferente do que você espera.

2
Tarefa
C# SELF, nível 53, lição 0
Bloqueado
Assinatura dinâmica e visualização de assinantes
Assinatura dinâmica e visualização de assinantes
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION