【问题标题】:what's the best way to implement chained events in C#在 C# 中实现链式事件的最佳方法是什么
【发布时间】:2009-07-12 08:08:35
【问题描述】:

我有以下情况。客户端代码只能访问 FooHandler,不能直接访问 Foo 实例。

public delegate void FooLoaded(object sender, EventArgs e);

class Foo {
    public event FooLoaded loaded;
    /* ... some code ... */
    public void Load() { load_asynchronously(); }
    public void callMeWhenLoadingIsDone() { loaded(this,EventArgs.Empty); }
}

class FooHandler {
    public event FooLoaded OneFooLoaded;

    /* ... some code ... */

    public void LoadAllFoos() { 
        foreach (Foo f in FooList) { 
            f.loaded += new FooLoaded(foo_loaded);
            f.Load(); 
        } 
    }

    void foo_loaded(object sender, EventArgs e) {
        OneFooLoaded(this, e);
    }

}

然后客户端将使用 FooHandler 类的 OneFooLoaded 事件来获取加载 foos 的通知。这种“事件链”是正确的做法吗?有其他选择吗?我不喜欢这样(感觉不对,我无法准确表达原因),但如果我希望处理程序成为访问点,我似乎没有很多选择。

【问题讨论】:

    标签: c# .net events


    【解决方案1】:

    如果感觉不对,因为事件比您的内部通信所需的更复杂和面向外部(考虑到事件可以调用多个客户而您知道只需要通知一个客户,我认为这至少部分正确,对吗?) ,那么我提出以下替代方案。不要使用事件将 Foo 的完成传递给 FooHandler,因为 Foo 无论如何都是内部的,你可以向 Foo 的构造函数或 Load 方法添加一个回调参数,Foo 可以在加载完成时调用它。如果你只有一个回调,这个参数可以只是一个函数,如果你有很多,它可以是一个接口。以下是我认为您的代码使用简化的内部接口后的外观:

    public delegate void FooLoaded(FooHandler sender, EventArgs e);
    
    class Foo
    {
      Action<Foo> callback;
      /* ... some code ... */
      public void Load(Action<Foo> callback) { this.callback = callback; load_asynchronously(); }
      public void callMeWhenLoadingIsDone() { callback(this); }
    }
    
    class FooHandler
    {
      public event FooLoaded OneFooLoaded;
    
      /* ... some code ... */
    
      public void LoadAllFoos()
      {
         foreach (Foo f in FooList)
         {
            f.Load(foo_loaded);
         }
      }
    
      void foo_loaded(Foo foo)
      {
         // Create EventArgs based on values from foo if necessary
         OneFooLoaded(this, null);
      }
    
    }
    

    请注意,这还允许您使用 FooLoaded 委托进行更强的类型化。

    另一方面,如果感觉不对,因为事件不应该通过 FooHandler 到达客户端,那么 1) 我会对此提出异议,因为如果客户端不想与个人打交道Foo 对象,它也不应该在该级别从它们接收事件,并且 2)如果你真的想这样做,即使 Foo 是私有的,你也可以在 Foo 上实现一些公共回调接口,或者使用像 Pavel 建议的机制.然而,我认为客户喜欢实现更少的事件处理程序并在一个处理程序中区分源的简单性,而不是必须连接(并可能断开)来自数十个较小对象的事件。

    【讨论】:

    • 我希望在开始我目前正在从事的项目之前看到这一点,这对我来说变得非常简单(并且更容易进行单元测试)。
    【解决方案2】:

    另一种方法是在所有事件都经过的域中创建一个点(单个类)。使用该域的任何类都将连接到该类,该类具有静态事件列表,并且该类将侦听该域中的任何内部类事件,从而至少避免该域中的事件链。

    参考资料:

    【讨论】:

    • 我刚刚发现这个并认为它很棒,比尝试连接 2 或 3 个事件并获得意大利面条要简单得多
    【解决方案3】:

    一些可能有用也可能没用的提示...

    这样写事件声明:

    public event FooLoaded loaded = delegate {};
    

    这样即使没有客户加入,您也可以安全地触发它。

    关于链接事件的主题,当您有两个事件时:

    public event EventHandler a = delegate {};
    public event EventHandler b = delegate {};
    

    您可能希望 b 的触发也导致 a 的触发:

    b += (s, e) => a(s, e);
    

    然后你可能会认为它会更简洁地说:

    b += a;
    

    确实,Resharper 甚至可能会向您推荐它!但这意味着完全不同的东西。它将a当前内容 附加到b,因此如果以后有更多处理程序使用a 登记,这不会导致它们在b 被触发时被调用。

    【讨论】:

      【解决方案4】:

      我可以告诉你,这种事件瀑布是我很自然地遇到过几次,我还没有遇到过严重的问题。

      虽然我不认为我曾经透明地传递事件,但总是伴随着语义变化。例如,FooLoaded 将变为 AllFoosLoaded。如果你想仅仅为了它而强加这样的语义更改,你可以将OneFooLoaded更改为百分比指示符(接收类是否需要知道加载了多少Foos?),例如。

      我认为这样的结构感觉不对,因为event 是用于广播的。它并没有真正对广播它的类强加契约,也没有对订阅它的类强加契约。

      但是,外观类和信息隐藏的一般原则旨在促进合同的执行。

      我仍在收集我对这个问题的想法,对不起,如果上述内容有点不清楚,但我不知道是否有更好的方法来做你想做的事。如果有的话,我和你一样有兴趣看到它。

      【讨论】:

        【解决方案5】:

        您可以将addremove 委托给事件,而不是提升:

        class FooHandler {
            public event FooLoaded OneFooLoaded {
               add { 
                   foreach (Foo f in FooList) {
                       f.loaded += new FooLoaded(value);
                   }
               }
               remove {
                   foreach (Foo f in FooList) {
                       f.loaded -= new FooLoaded(value);
                   }
               }
            }
        
            public void LoadAllFoos() { 
                foreach (Foo f in FooList) { 
                    f.Load(); 
                } 
            }
        }
        

        以上假设FooListFooHandler 的生命周期内是不可变的。如果它是可变的,那么您还必须跟踪对其添加/删除的项目,并相应地添加/删除处理程序。

        【讨论】:

          猜你喜欢
          • 2011-05-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多