CodeGym /Cursos /C# SELF /Cenários práticos e melhores práticas

Cenários práticos e melhores práticas

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

1. Cenários práticos de uso de eventos

Eventos — não são só teoria bonita dos livros. Na prática eles são necessários em quase todo segundo aplicativo, e se você trabalha com UI ou serviços de rede — na maioria dos casos.

Vamos ver alguns cenários típicos do desenvolvimento real. De quebra veremos como padrões e melhores práticas ajudam a evitar erros comuns.

Reagindo a ações do usuário (como na UI)

Talvez o cenário mais comum — quando o usuário faz algo (clica num botão, seleciona um item na lista), e a aplicação precisa reagir. É assim que Windows Forms, WPF, UWP, Avalonia, MAUI e outros frameworks UI funcionam.

Mini-exemplo (sem UI — simulando um botão):


public class Button
{
    public event EventHandler? Click; // delegado padrão

    public void SimulateClick()
    {
        Click?.Invoke(this, EventArgs.Empty); // o "Botão" foi "clicado"
    }
}

class Program
{
    static void Main()
    {
        var button = new Button();
        button.Click += (sender, e) => Console.WriteLine("Botão clicado!");
        button.SimulateClick(); // > Botão clicado!
    }
}

Em um framework UI real o evento Click é acionado quando o usuário clica com o mouse ou toca na tela. Tudo que estiver inscrito nesse evento receberá a notificação — é assim que a lógica das aplicações funciona.

Sinalizar progresso, conclusão de operações e erros

Imagine: download de arquivo, processamento de dados, cálculo longo. É preciso informar sobre o progresso ou sobre erros. Frequentemente se organiza um evento para "passo de progresso" e outro para "concluído" ou "erro".

Exemplo de downloader de arquivos:


public class FileDownloader
{
    public event EventHandler<int>? ProgressChanged;
    public event EventHandler? DownloadCompleted;
    public event EventHandler<string>? DownloadFailed;

    public void Download()
    {
        for (int i = 1; i <= 100; i += 10)
        {
            Thread.Sleep(50); // simulação de atraso
            ProgressChanged?.Invoke(this, i);
        }
        DownloadCompleted?.Invoke(this, EventArgs.Empty);
    }
}

var downloader = new FileDownloader();
downloader.ProgressChanged += (s, progress) => Console.WriteLine($"Download: {progress}%");
downloader.DownloadCompleted += (s, e) => Console.WriteLine("Download concluído.");
downloader.Download();

Aqui há vários eventos: você pode assinar todos de uma vez (ou só os que interessam). Isso lembra como muitos classes padrão do .NET para threads, download de arquivos, HTTP etc. funcionam.

Reação ao ciclo de vida de objetos (por exemplo, "salvo", "removido")

Suponha que você tem um modelo de domínio. Você quer que, quando um usuário salvar ou deletar um objeto, alguns processos reajam: atualizem o cache, o log, enviem uma mensagem e por aí vai.


public class UserRepository
{
    public event EventHandler<UserEventArgs>? UserSaved;
    public event EventHandler<UserEventArgs>? UserDeleted;

    public void Save(User user)
    {
        //... salvamos o usuário
        UserSaved?.Invoke(this, new UserEventArgs(user));
    }

    public void Delete(User user)
    {
        //... deletamos o usuário
        UserDeleted?.Invoke(this, new UserEventArgs(user));
    }
}

public class UserEventArgs : EventArgs
{
    public User User { get; }
    public UserEventArgs(User user) => User = user;
}

Agora diferentes módulos podem assinar e — sem ligações diretas! — receber notificações sobre mudanças nos usuários. E o repositório em si não sabe quem "se conectou".

Eventos assíncronos e multithreading

Às vezes eventos são gerados fora da thread principal (UI) — por exemplo em tarefas em background, timers ou operações assíncronas. Nesses casos é importante lembrar que o código dos handlers será executado em uma "thread estranha". Se o handler tentar atualizar elementos de UI diretamente de outra thread — isso causará erros.

O que é marshalling? Marshalling é mover a execução do código de uma thread para outra, normalmente da thread de background de volta para a thread de UI, para atualizar a interface com segurança. Em aplicações UI (WinForms, WPF) usam-se mecanismos como SynchronizationContext ou Dispatcher, que permitem "redirecionar" a chamada do handler para a thread correta.

Não faça assim:


// Evento é chamado de uma thread de background, e o handler atualiza a UI diretamente — vai gerar exceção!

Recomendado: verifique a thread de execução e, se necessário, faça o marshalling para a thread de UI (via SynchronizationContext ou Dispatcher). Em apps de console e servidores normalmente não há essas restrições.

Event Aggregator / Messaging

Em aplicações grandes frequentemente usam um agregador centralizado de eventos (Event Aggregator). Ele reduz acoplamento: subscribers e publishers não sabem um do outro e trocam mensagens através do hub.


public class EventAggregator
{
    public event EventHandler<SomeEventArgs>? SomeEvent;

    public void Publish(SomeEventArgs args)
    {
        SomeEvent?.Invoke(this, args);
    }
}

2. Melhores padrões e práticas

Use o atributo [CallerMemberName] para erros

Se você cria código de infraestrutura (por exemplo, loggers, tracers), logue o nome do método-fonte do evento usando o atributo CallerMemberName — isso facilita a diagnóstico.

Faça eventos thread-safe quando necessário

Eventos são multicast, e acessos de diferentes threads exigem cuidado: antes de invocar copie o delegate para uma variável local.


var handler = MyEvent;
if (handler != null) handler(this, args);

Em C# 6+ use a chamada segura: MyEvent?.Invoke(this, args).

Projete seus EventArgs personalizados como imutáveis

Defina seus tipos de EventArgs com propriedades somente leitura — isso previne mudanças acidentais do estado do evento pelos assinantes.


public class WorkCompletedEventArgs : EventArgs
{
    public string Message { get; }
    public WorkCompletedEventArgs(string message) => Message = message;
}

public ou private event

Deixe eventos public event, e encapsule a invocação em um método protected virtual chamado OnEventName. Isso garante extensibilidade (um derivado pode sobrescrever o comportamento), e consumidores externos não podem disparar o evento diretamente.

Não quebre a sequência do ciclo de vida

Se você inicia uma longa cadeia de handlers, lembre: alguém pode se inscrever e tentar alterar a lógica interna. Documente onde e quando os eventos são chamados, e se um evento pode disparar várias vezes.

Múltiplos eventos e composição

Quando um objeto escuta várias fontes, agrupe inscrições e desinscrições num só lugar (por exemplo, métodos Subscribe/Unsubscribe). Se necessário, mantenha campos privados de delegate para facilitar a desinscrição.

3. Como NÃO usar eventos: erros típicos

Erro nº 1: ignorar o padrão padrão de eventos.
Se cada classe declarar seus próprios delegates para cada evento, isso vira uma bagunça rápido. Tente usar delegates padrão EventHandler ou EventHandler<T>, onde T herda de EventArgs. Essa abordagem torna o código mais legível e familiar para desenvolvedores .NET.

Erro nº 2: invocar evento incorretamente de código externo.
Nunca invoque um evento diretamente fora da classe-publicadora. O evento deve ser iniciado apenas dentro da classe, geralmente via método protegido OnEventName. Os assinantes só têm direito de assinar (+=) e desinscrever (-=).

Errado:


foo.MyEvent(); // erro! Não chame o evento diretamente de fora

Certo:


// Só dentro de foo:
// protected virtual void OnMyEvent()
// {
//     MyEvent?.Invoke(this, EventArgs.Empty);
// }

Erro nº 3: chamar evento sem checar se existem assinantes (null).
Se tentar invocar um evento sem assinantes, você obterá um NullReferenceException. Em C# 6.0+ use ?.Invoke(...) — o evento só será chamado se houver assinantes.

Erro nº 4: esquecer de desinscrever do evento (vazamento de memória).
Se um objeto se inscreve num evento e não se desinscreve antes de ser coletado, o publisher mantém a referência ao assinante. O GC não consegue liberar a memória, o que é crítico quando publishers vivem muito e assinantes são curtos (por exemplo, ViewModel, forms). A melhor solução é desinscrição explícita ou usar referências fracas (WeakReference) quando apropriado.

Erro nº 5: invocar evento fora de um método protegido virtual OnEvent.
Invocando o evento diretamente você tira dos derivados a chance de sobrescrever o comportamento corretamente. O padrão correto é declarar um método protected virtual que chama o evento.

Exemplo padrão:


protected virtual void OnSomething(EventArgs e)
{
    Something?.Invoke(this, e);
}
1
Pesquisa/teste
Ciclo de Vida dos Eventos, nível 53, lição 4
Indisponível
Ciclo de Vida dos Eventos
Análise detalhada de como assinar eventos
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION