Partilhar via


O padrão de evento .NET Core atualizado

Anterior

O artigo anterior discutiu os padrões de eventos mais comuns. O .NET Core tem um padrão mais descontraído. Nesta versão, a definição de EventHandler<TEventArgs> não tem mais a restrição de que TEventArgs deve ser uma classe derivada de System.EventArgs.

Isso aumenta a sua flexibilidade e garante compatibilidade com versões anteriores. Comecemos pela flexibilidade. A implementação para System.EventArgs usa um método definido em System.Object um método: MemberwiseClone(), que cria uma cópia superficial do objeto. Esse método deve usar a reflexão para implementar sua funcionalidade para qualquer classe derivada de EventArgs. Essa funcionalidade é mais fácil de criar em uma classe derivada específica. Isso efetivamente significa que derivar de System.EventArgs é uma restrição que limita seus projetos, mas não fornece nenhum benefício extra. Na verdade, você pode alterar as definições de FileFoundArgs e SearchDirectoryArgs para que elas não derivem de EventArgs. O programa funciona exatamente da mesma forma.

Você também pode alterar o SearchDirectoryArgs para uma estrutura, se fizer mais uma alteração:

internal struct SearchDirectoryArgs
{
    internal string CurrentSearchDirectory { get; }
    internal int TotalDirs { get; }
    internal int CompletedDirs { get; }

    internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs) : this()
    {
        CurrentSearchDirectory = dir;
        TotalDirs = totalDirs;
        CompletedDirs = completedDirs;
    }
}

A alteração extra é chamar o construtor sem parâmetros antes de inserir o construtor que inicializa todos os campos. Sem essa adição, as regras do C# informariam que as propriedades estão sendo acessadas antes de serem atribuídas.

Você não deve alterar o FileFoundArgs de uma classe (tipo de referência) para uma struct (tipo de valor). O protocolo para lidar com cancelamento requer que passe os argumentos do evento por referência. Se você fez a mesma alteração, a classe de pesquisa de arquivo nunca poderá observar quaisquer alterações feitas por qualquer um dos assinantes do evento. Uma nova cópia da estrutura seria usada para cada assinante, e essa cópia seria uma cópia diferente da vista pelo objeto de pesquisa de arquivo.

Em seguida, vamos considerar como essa alteração pode ser compatível com versões anteriores. A remoção da restrição não afeta nenhum código existente. Todos os tipos de argumento de evento existentes ainda derivam de System.EventArgs. A compatibilidade com versões anteriores é uma das razões principais pelas quais eles continuam a derivar de System.EventArgs. Todos os subscritores de eventos existentes são subscritores de um evento que seguiu o padrão clássico.

Seguindo uma lógica semelhante, qualquer tipo de argumento de evento criado agora não teria assinantes em nenhuma base de código existente. Novos tipos de evento que não derivam de System.EventArgs não quebram essas bases de código.

Eventos com subscritores Async

Você tem um padrão final para aprender: Como escrever corretamente subscritores de eventos que executam código assíncrono. O desafio é descrito no artigo sobre assíncrono e aguardar. Os métodos assíncronos podem ter um tipo de retorno vazio, mas isso é desencorajado. Quando o código do assinante do evento chama um método assíncrono, você não tem escolha a não ser criar um método async void. A assinatura do manipulador de eventos requer isso.

É preciso conciliar essa orientação oposta. De alguma forma, você deve criar um método de async void seguro. Os conceitos básicos do padrão que você precisa implementar são mostrados no código a seguir:

worker.StartWorking += async (sender, eventArgs) =>
{
    try
    {
        await DoWorkAsync();
    }
    catch (Exception e)
    {
        //Some form of logging.
        Console.WriteLine($"Async task failure: {e.ToString()}");
        // Consider gracefully, and quickly exiting.
    }
};

Primeiro, observe que o manipulador está marcado como um manipulador assíncrono. Como está sendo atribuído a um tipo de delegado do manipulador de eventos, ele tem um tipo de retorno vazio. Isso significa que você deve seguir o padrão mostrado no manipulador e não permitir que nenhuma exceção seja descartada do contexto do manipulador assíncrono. Como não retorna uma tarefa, não há nenhuma tarefa que possa relatar o erro ao entrar no estado de falha. Como o método é assíncrono, o método não pode lançar a exceção. (O método de chamada continua a execução porque é async.) O comportamento real do tempo de execução é definido de forma diferente para diferentes ambientes. Ele pode encerrar o thread ou o processo que possui o thread, ou deixar o processo em um estado indeterminado. Todos estes potenciais resultados são altamente indesejáveis.

Você deve envolver a expressão await para a Tarefa assíncrona em seu próprio bloco de tentativa. Se isso causar uma tarefa com defeito, você poderá registrar o erro. Se for um erro do qual seu aplicativo não pode se recuperar, você pode sair do programa de forma rápida e graciosa

Este artigo explicou as principais atualizações para o padrão de evento .NET. Poderá ver muitos exemplos das versões anteriores nas bibliotecas com que trabalha. No entanto, você deve entender quais são os padrões mais recentes também. Você pode ver o código concluído para o exemplo em Program.cs.

O próximo artigo desta série ajuda-o a distinguir entre a utilização de delegates e events nos seus desenhos ou modelos. São conceitos semelhantes, e esse artigo ajuda-o a tomar a melhor decisão para os seus programas.

Próximo