O padrão de evento .NET Core atualizado
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 EventHandler<TEventArgs>
definição não tem mais a restrição que TEventArgs
deve ser uma classe derivada de System.EventArgs
.
Isso aumenta a flexibilidade para você e é compatível com versões anteriores. Comecemos pela flexibilidade. A classe System.EventArgs introduz 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 adicional.
Na verdade, você pode alterar as definições de e SearchDirectoryArgs
para FileFoundArgs
que elas não derivem de EventArgs
.
O programa funcionará exatamente da mesma forma.
Você também pode alterar o SearchDirectoryArgs
para um struct, 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 adicional é 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 um struct (tipo de valor). Isso ocorre porque o protocolo para lidar com o cancelamento exige que os argumentos do evento sejam passados 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 principais razões pelas quais eles continuarão a derivar do System.EventArgs
. Todos os subscritores de eventos existentes serã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 não quebrarão essas bases de System.EventArgs
código.
Eventos com subscritores Async
Você tem um padrão final para aprender: Como escrever corretamente assinantes de eventos que chamam 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 é fortemente 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 async void
método. A assinatura do manipulador de eventos requer isso.
É preciso conciliar essa orientação oposta. De alguma forma, você deve criar um método seguro async void
. Os conceitos básicos do padrão que você precisa implementar estão abaixo:
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 ele está sendo atribuído a um tipo de delegado do manipulador de eventos, ele terá 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 ele não retorna uma tarefa, não há nenhuma tarefa que possa relatar o erro inserindo o estado com falha. Como o método é assíncrono, o método não pode simplesmente lançar a exceção. (O método de chamada continuou a execução porque é async
.) O comportamento real do tempo de execução será 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.
É por isso que você deve encapsular a instrução await para a tarefa assíncrona em seu próprio bloco try. 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 rapidamente e graciosamente
Essas são as principais atualizações para o padrão de eventos .NET. Você verá muitos exemplos das versões anteriores nas bibliotecas com as quais trabalha. No entanto, você deve entender quais são os padrões mais recentes também.
O próximo artigo desta série ajuda-o a distinguir entre a utilização delegates
e events
os seus desenhos ou modelos. São conceitos semelhantes, e esse artigo irá ajudá-lo a tomar a melhor decisão para os seus programas.