更新后的 .NET Core 事件模式

以前

上一篇文章讨论了最常见的事件模式。 .NET Core 具有更宽松的模式。 在此版本中,EventHandler<TEventArgs>定义不再需要TEventArgs必须是派生自System.EventArgs的类的约束。

这就提高了灵活性,并且还具有后向兼容性。 让我们从灵活性开始。 用于 System.EventArgs 的实现方法使用了在 System.Object 中定义的方法:MemberwiseClone(),它将创建对象的浅表副本。 该方法必须使用反射来实现其派生自 EventArgs的任何类的功能。 该功能更易于在特定派生类中创建。 实际上,这意味着派生自 System.EventArgs 的类会限制你的设计,且不会为你提供任何附加好处。 事实上,你可以更改其定义 FileFoundArgsSearchDirectoryArgs 以便它们不派生自 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.)对于不同的环境,以不同的方式定义实际运行时行为。 它可能会终止线程或拥有该线程的进程,或使进程处于不确定状态。 所有这些潜在结果都是非常不受欢迎的。

应在自己的 try 块中封装异步任务的 await 表达式。 如果它确实导致任务出错,可以记录错误。 如果这是应用程序无法恢复的错误,则可以快速正常地退出程序

本文介绍了 .NET 事件模式的主要更新。 你可能会在要使用的库中看到早期版本的许多示例。 但是,还应了解最新的模式。 可以在 Program.cs查看示例的已完成代码。

本系列中的下一篇文章将帮助你区分在设计中使用delegatesevents的方法。 它们是类似的概念,本文可帮助你为程序做出最佳决策。

下一步