Обновленный шаблон событий .NET Core

Предыдущий

В предыдущей статье рассматривались наиболее распространенные шаблоны событий. .NET Core имеет менее жесткий шаблон. В этой версии определение EventHandler<TEventArgs> больше не имеет ограничения, указывающего на то, что TEventArgs должен быть классом, производным от System.EventArgs.

В результате повышается гибкость разработки и обеспечивается обратная совместимость. Начнем с гибких возможностей. Класс System.EventArgs представляет один метод: MemberwiseClone(), который создает неполную копию объекта. Чтобы реализовать свою функциональность для любого класса, производного от EventArgs, этот метод должен использовать отражение. Функциональность проще создать в определенном производном классе. Это фактически означает, что наследование от System.EventArgs является ограничением, которое регламентирует разработки, но не предоставляет никаких дополнительных преимуществ. На самом деле, можно изменить определения FileFoundArgs и SearchDirectoryArgs так, чтобы они не были производными от EventArgs. Программа будет работать точно так же.

Вместо SearchDirectoryArgs можно также использовать структуру, внеся дополнительное изменение.

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;
    }
}

Дополнительное изменение заключается в вызове конструктора без параметров перед входом в конструктор, который инициализирует все поля. Без этого согласно правилам языка C# будет выведено сообщение о получении доступа к свойствам до их назначения.

Не следует изменять FileFoundArgs с класса (ссылочный тип) на структуру (тип значения). Это связано с тем, что протокол для обработки отмены требует передачи аргументов события по ссылке. Если вы выполнили то же изменение, класс поиска файла никогда не сможет отслеживать изменения, внесенные подписчиками событий. Для каждого подписчика будет использоваться новая копия, которая будет отличаться от той, которую обнаружил объект поиска файла.

Теперь давайте рассмотрим обратную совместимость этого изменения. Удаление ограничения не влияет на существующий код. Все имеющиеся типы аргументов событий по-прежнему являются производными от System.EventArgs. Обратная совместимость является одной из основных причин, по которой они будут и далее являться производными от System.EventArgs. Все существующие подписчики на события будут подписчиками на событие, следующее классическому шаблону.

Согласно аналогичной логике, теперь тип события аргумента не будет иметь подписчиков в существующих базах кода. Новые типы событий, которые не являются производными от System.EventArgs, не нарушат функциональность этих баз кода.

События с асинхронными подписчиками

Вам осталось изучить последний шаблон: как правильно написать подписчики событий, которые вызывают асинхронный код. Это описано в статье, посвященной async и await. Асинхронные методы могут иметь возвращаемый тип void, однако это крайне не рекомендуется. Когда код подписчика событий вызывает асинхронный метод, вам придется создать метод async void. Это необходимо для сигнатуры обработчика событий.

Вы должны найти правильное решение. Каким-то образом необходимо создать безопасный метод async void. Ниже приведены основные сведения о шаблоне, которым необходимо следовать.

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.
    }
};

Во-первых, обратите внимание на то, что обработчик помечен как асинхронный обработчик. Поскольку он назначается типу делегата обработчика событий, он будет иметь возвращаемый тип void. Это означает, что необходимо следовать шаблону, приведенному в обработчике, и не допускать создания исключений вне контекста асинхронного обработчика. Поскольку он не возвращает задачу, ничто не сообщает об ошибке при переходе в состояние сбоя. Поскольку метод является асинхронным, он просто не может создать исключение. (Вызывающий метод продолжал выполнение, так как это async.) Фактическое поведение во время выполнения будет определяться по-разному для разных сред. Оно может завершить поток или процесс, владеющий потоком, или оставить процесс в неопределенном состоянии. Все эти возможные результаты очень нежелательны.

Вот почему необходимо заключить оператор await для асинхронных задач в собственный блок try. Если он приводит к сбою задачи, можно записать ошибку в журнал. Если это ошибка, из-за которой невозможно восстановить приложение, можно быстро и правильно выйти из программы.

Это были основные обновления шаблона событий .NET. Затем вы увидите множество примеров предыдущих версий в библиотеках, с которыми вы работаете. Однако также необходимо иметь представление и о последних шаблонах.

В следующей статье этой серии материалов вы узнаете об использовании delegates и events в своих проектах. Это относительно схожие понятия, и сведения в этой статье помогут вам принять оптимальное решение.

Далее