事件约定 - 我不理解

6

使用事件的类:

public class WindowModel
{
    public delegate void WindowChangedHandler(object source, WindowTypeEventArgs e);
    public event WindowChangedHandler WindowChanged;  

    public void GotoWindow(WindowType windowType)
    {
        this.currentWindow = windowType;
        this.WindowChanged.Invoke(this, new WindowTypeEventArgs(windowType));
    }
}

派生事件类:

public class WindowTypeEventArgs : EventArgs
{
    public readonly WindowType windowType;

    public WindowTypeEventArgs(WindowType windowType)
    {
        this.windowType = windowType;
    }
}

其他注册到该事件的类:


private void SetupEvents()
{
   this.WindowModel.WindowChanged += this.ChangeWindow;
}

private void ChangeWindow(object sender, WindowTypeEventArgs e)
{
   //change window
}

我从遵循 .Net 约定中获得了什么? 像这样有一个契约更有意义。
public delegate void WindowChangedHandler(WindowType windowType);
public event WindowChangedHandler WindowChanged;

用这种方法,我不需要创建一个新的类,而且更容易理解。 我没有编写一个.Net库。这段代码只会在这个项目中使用。我喜欢约定,但是在这个例子中我是否正确地理解了它或者我误解了什么?

3个回答

15

单独看,你是正确的:.NET常规语法更冗长、不太直观,但有其优点:

  • 未来对事件传递信息的更改并不会自动要求更改每一个使用该事件的消费者。例如,如果你想向事件添加额外的信息——比如WindowTitle字符串——那么你必须修改附加到该事件的每个函数的签名,无论它们是否使用该信息。而采用 EventArgs方法,你只需将该属性添加到参数中,并仅修改需要利用额外信息的函数。
  • 自从.NET 2.0引入了EventHandler<TEventArgs>委托类型以后,你就不再需要手动定义自己的事件委托了。在这个例子中,你可以将事件类型定义为 EventHandler<WindowTypeEventArgs>,而不是 WindowChangedHandler
  • EventArgs方法使得传递多种信息类型回调函数变得容易。如果你需要在另一个示例中(直接传递事件参数的示例)这样做,你最终仍然得创建自己的元组类来保存信息。

当你看实际的.NET事件模式时,第一个优点更加明显,实际上是通过创建一个实现protected virtual函数来进行调用。例如:

public event EventHandler<WindowTypeEventArgs> WindowChanged;

protected virtual void OnWindowChanged(WindowTypeEventArgs e)
{
    var evt = WindowChanged;

    if(evt != null) evt(this, e);
}

我想指出几点:

  1. 使用创建此事件调用方法的模式使您可以避免在代码中进行空值检查(没有任何附加到其上的函数的事件将为null,如果您尝试调用它,则会引发异常)。
  2. 这种模式还允许从您继承的类控制调用顺序,使它们明确地在外部使用者之前或之后执行其代码。
  3. 这在多线程环境中尤其重要。如果您只是说if(WindowChanged != null) WindowChanged(this, e);,您实际上会冒着在检查和调用之间WindowChanged事件变为null的风险。这在单线程场景中不重要,但形成这种良好的防御习惯非常重要。

我真的很喜欢这个答案!第一点非常明显,但是我没有想到。第二点我不知道,但是很好 :) 不确定我理解你的第三点。谢谢! - Mads Andersen
@bobjink:很高兴它有帮助!我的第三点是关于能够将数据发送回调用事件的对象。使用EventArgs方法,您可以允许事件的消费者修改其上的属性,例如CancelEventArgs类上的属性,这将允许它将数据传递回您。 - Adam Robinson

3
我理解您的困惑!当我第一次看到这个时,我也有同样的感觉。
重要的是要意识到,从编程的角度来看,它并没有给你太多的优势,但它是一个公认的约定。因此,有很多工具期望 void EventName(object sender, EventArgs e) 签名。例如,一些 IoC 容器可以使用此签名在构建时自动连接事件。
简而言之,它看起来有点奇怪,但这是一种约定。坚持下去,灯泡最终会亮起来!

1

你可以使用你的委托。没有人会强迫你。这只是一个处理事件的好模式。

如果你使用标准的 Sender-EventArgs 模式,你将能够在其他事件中也使用同样的 ChangeWindow 处理程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接