2009-07-12 67 views
6

我有以下情形。客户端代码只能访问FooHandler,而不是直接访问Foo实例。在C中实现链接事件的最佳方式是什么?

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的通知。这个'事件链'是正确的事情吗?有没有其他的选择?我不喜欢这样(感觉不对,我无法准确表达原因),但是如果我希望处理程序成为访问点,我似乎没有多少选择。

回答

4

如果因为事件比必要的更加复杂和朝外的用于内部通信(我认为至少是部分正确的考虑事件可以调用多个客户,而你知道你只需要通知一个,右感觉错了吗? ),那么我建议下面的选择。而不是使用事件进行沟通的Foo到FooHandler完成,因为foo是内部反正,你可以回调参数添加到富的构造函数或Load方法,当载入完成美孚可以拨打。如果你只有一个回调函数,这个参数可以只是一个函数,或者如果你有很多回调函数,它可以是一个接口。以下是我想你的代码看起来与简化内部接口:

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)我会质疑,因为如果客户不想处理个人富的对象,不应该在这个水平灌入从他们的活动,和2)如果你真的想这样做,你可以实现在富一些公共的回调接口,即使富是私有的,或者使用像帕维尔机制建议。我想,但是,客户喜欢落实更少事件处理程序和区分一个处理器内的源,而不是从几十个更小的物体的连接(也可能断开)事件的简单性。

+0

我想开始我目前工作的项目之前,我曾见过这个简单得多,这让我的事情变得如此简单(并且更容易进行单元测试)。 – ForbesLindesay 2010-12-17 15:06:46

1

我可以告诉你,这种事件瀑布是我多次相当自然地到达的东西,我还没有遇到与他们有严重问题。

尽管我不认为自己曾经透明地传递过事件,但总是伴随着语义变化。例如,FooLoaded将变成AllFoosLoaded。如果您只是为了实现这种语义变化,您可以将OneFooLoaded更改为百分比指示符(接收类是否需要知道装入了多少个Foo?)。

我认为这样的结构感觉不对,因为event是用于广播。它并没有真正对广播它的班级施加合同,也没有对订阅该班级的班级强加合同。

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

我还在收集我对这个问题的想法,对不起,如果上面有点不清楚,但我不知道是否有更好的方法来做你想做的事。如果有的话,我有兴趣看到它,因为你是。

1

你可以在事件委托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(); 
     } 
    } 
} 

上述假设FooList是不可变的FooHandler寿命。如果它是可变的,那么你还必须跟踪对它添加/删除项目,并相应地添加/删除处理程序。

3

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

参考文献:

+0

我刚刚发现这一点,并认为它太棒了,这么多的不是试图线了2点或3的事件,并得到意大利面条 – Calanus 2009-07-31 11:03:59

2

夫妇的提示,可能会或可能不会有帮助的......

写事件的声明是这样的:

public event FooLoaded loaded = delegate {}; 

即使没有客户入伍,您也可以安全地开火。

在链接事件的主题,当你有两个事件:

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

您可能希望B的发射也引起的射击:

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

然后您会看这一点,认为这将是更简洁地说:

b += a; 

事实上,ReSharper的甚至会建议它给你!但它意味着完全不同的东西。它将当前内容附加到ab,所以如果后面的更多处理程序加入a,这将不会导致它们在b被触发时被调用。

相关问题