【问题标题】:Are there pitfalls to using static class/event as an application message bus使用静态类/事件作为应用程序消息总线是否存在陷阱
【发布时间】:2010-05-13 19:30:31
【问题描述】:

我有一个静态泛型类,它可以帮助我以很少的开销移动事件:

public static class MessageBus<T> where T : EventArgs
{
    public static event EventHandler<T> MessageReceived;
    public static void SendMessage(object sender, T message)
    {
        if (MessageReceived != null)
            MessageReceived(sender, message);
    }
}

要创建系统范围的消息总线,我只需要定义一个 EventArgs 类来传递任意位的信息:

class MyEventArgs : EventArgs
{
    public string Message { get; set; }
}

只要我对这个事件感兴趣,我就连接一个处理程序:

MessageBus<MyEventArgs>.MessageReceived += (s,e) => DoSomething();

同样,触发事件同样简单:

MessageBus<MyEventArgs>.SendMessage(this, new MyEventArgs() {Message="hi mom"});

使用 MessageBus 和自定义 EventArgs 类可以让我为特定类型的消息提供应用程序范围的消息接收器。当您有多个表单(例如,显示客户信息)以及可能更新该信息的几个表单时,这会派上用场。所有表单都不知道彼此,也不需要连接到静态“超类”。

我有几个问题:

  1. fxCop 抱怨使用带有泛型的静态方法,但这正是我在这里所追求的。我希望处理的每种消息类型都只有一个 MessageBus。使用带有泛型的静态使我免于编写维护 MessageBus 对象列表的所有代码。

  2. 监听对象是否通过 MessageReceived 事件保持“活动”?

例如,也许我在 Form.Load 事件中有这段代码:

MessageBus<CustomerChangedEventArgs>.MessageReceived += (s,e) => DoReload();

当窗体关闭时,窗体是否保留在内存中,因为 MessageReceived 引用了它的 DoReload 方法?我是否应该在表单关闭时删除引用:

MessageBus<CustomerChangedEventArgs>.MessageReceived -= (s,e) => DoReload();

【问题讨论】:

  • 很多关于#2 的有用反馈...谢谢!有人在#1 上有 cmets 吗?为什么认为使用这种方法“不好”?

标签: c# generics events


【解决方案1】:

嗯,是的,你应该这样做,但是如果你像在你的示例中那样使用 lambda 语法,我认为它不会起作用(我的意思是,处理程序不会被成功注销)。

如果我错了,请有人纠正我,但我相信这是真的,因为使用 lambda 语法有效地创建了一个新的 EventHandler&lt;CustomerChangedEventArgs&gt; 对象,它在内存中拥有自己的位置。当您尝试删除此处理程序时,再次使用 lambda 语法,这会创建 另一个 新的 EventHandler&lt;CustomerChangedEventArgs&gt; 对象,它不等于您创建的第一个对象;所以第一个永远不会被取消注册。

遗憾的是,我认为您实际上需要定义这样的方法:

DoReload(object sender, CustomerChangedEventArgs e) {
    DoReload(); // your original overload, which doesn't actually care
                // about the sender and e parameters
}

这样你就可以做到:

MessageBus<CustomerChangedEventArgs>.MessageReceived += DoReload;

后来:

MessageBus<CustomerChangedEventArgs>.MessageReceived -= DoReload;

【讨论】:

  • 感谢您指出我对 Lambdas 的误解。你是绝对正确的......它们不能从事件处理程序中删除。
【解决方案2】:

是的,有问题。您的事件处理程序将导致表单对象保持引用,您必须显式取消注册事件处理程序。 lambda 使这成为不可能,您必须编写一个显式处理程序。

此模式有一个名称“Event Broker service”。它是复合 UI 应用程序块的一部分,由 Microsoft 的模式和实践团队发布。求、借、偷(如果不使用)你能从中得到的东西。

【讨论】:

  • "lambdas 使这成为不可能..." 您始终可以存储事件处理程序的实例(即使使用 lambda 表达式构造)并使用相同的实例来注册和注销事件。当然,这可能需要添加一个字段来存储实例。
  • “它是复合 UI 应用程序块的一部分...”感谢您将我指向此资源。
【解决方案3】:

您可以使用弱引用来存储事件处理程序。这样,未挂钩的处理程序就不会阻止对象的垃圾收集。

public static class MessageBus<T> where T : EventArgs
{
    private static List<WeakReference> _handlers = new List<WeakReference>();

    public static event EventHandler<T> MessageReceived
    {
        add
        {
            _handlers.Add(new WeakReference(value));
        }
        remove
        {
            // also remove "dead" (garbage collected) handlers
            _handlers.RemoveAll(wh => !wh.IsAlive  || wh.Target.Equals(value));
        }
    }

    public static void SendMessage(object sender, T message)
    {
        foreach(var weakHandler in _handlers)
        {
            if (weakHandler.IsAlive)
            {
                var handler = weakHandler.Target as EventHandler<T>;
                handler(sender, message);
            }
        }            
    }
}

【讨论】:

  • 有趣的方法。我会摆弄一下。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-11
  • 2011-09-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多