# 原则30:选择重载而不是事件处理器
**By D.S.Qiu**
**尊重他人的劳动,支持原创,转载请注明出处:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
很多 .NET 类提供两种方式处理系统事件。你可以附加指定一个事件处理器或者重载基类的虚函数。为什么提供两种方式做同一件事情?因为不同的情况需要不同的解决方案,这就是为什么。在子类中,你总可以重载虚函数。这样不相关对象就不能使用这个事件处理器。你可以写的一个漂亮的 Windows Presentation Foundation (WPF) 应用,需要响应鼠标按下事件。在你的 Form 类中,你可以选择重载 OnMouseDown 方法:
```
public partial class Window1 : Window
{
// other code elided
public Window1()
{
InitializeComponent();
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
DoMouseThings(e);
base.OnMouseDown(e);
}
}
```
或者,你也可以指定一个事件处理器(需要 C# 和 XAML ):
```
<!-- XAML File -->
<Window x:Class="Item36_OverridesAndEvent.Window1" xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300" MouseDown="OnMouseDown">
<Grid>
</Grid>
</Window>
// C Sharp file:
public partial class Window1 : Window
{
// other code elided
public Window1()
{
InitializeComponent();
}
private void OnMouseDown(object sender,MouseButtonEventArgs e)
{
DoMouseThings(e);
}
private void DoMouseThings(MouseButtonEventArgs e)
{
throw new NotImplementedException();
}
}
```
第一个方案是更好的。WPF 程序中声明代码是重点这得可能令人惊讶。即使这样,如果逻辑代码需要用代码实现,你应该使用虚函数。如果一个事件处理器抛出异常了,在链中的其他处理器就不会被调用(查看原则24和25)。通过重载 protected 需函数,你的处理器就可以被先调用。基类的虚函数版本是负责处理协议特殊的事情。这意味着如果你想要事件处理器被调用(并且你总是这样做的),你必须调用基类的虚函数。在一些少数的情况,你不想要默认的行为,你就不调用基类的版本这样就没有处理器被调用。你不能保证所有的事件处理器会被调用因为有些不正确的事件处理器会抛出异常,但是你可以保证你的子类行为是正确的。
使用重载比附加事件处理器更高效。你应该记得原则25讲到的事件是一个多播委托。这就使得一个事件源会有多个观察者。这个事件机制会占用处理器多点时间,因为他必须检查事件是否有添加事件处理器。如果有,它必须遍历整个调用队列,而这个队列可能包含了任意数量的目标函数。队列的每个方法都会被调用。检查是否有事件处理器并在运行时遍历调用会比只调用一个虚函数话费更多时间。
如果这个还不够,再查看下上面的两个例子。哪个更清晰?重载一个虚函数只需要检查一个函数并且如果维护这个表单只需要修改这个函数。事件机制有两个地方需要维护:事件处理器函数和关联事件的代码。这两个都会导致事件失败。一个函数就很更简单了。
好了,我已经给出了使用重载而不是事件处理器的所有理由。那 .NET 框架设计者确定有必要添加事件处理器么?当然有必要。除了我们,他们太忙以至于不会写没有人用的代码。重载只是在子类使用。其他的类都必须使用事件机制。这意味这在 XAML 文件中定义声明就可以使用事件处理器。在上面的例子中,你的设计者可能会有鼠标按下的事件的行为。设计者可以创建 XAML 声明它们的行为。这些行为可以响应表单事件。你可以在代码中重定义所有行为,但那会花更多时间处理一个事件。这只是将设计者的问题转移给你。你清楚自己想要设计者为你处理设计工作。更好的方式是创建一个事件然后通过设计工具创建 XMAL 声明。所以最后,你创建一个新的类并发送事件到表单类。这样一开始就添加一个事件处理器到表单会更简单。毕竟,这就是为什么 .NET 框架设计者要在表单添加事件。
事件机制的另外一个理由是事件是在运行时被链接的。使用事件会有个更大的灵活性。你可以根据程序的不同状态链接不同的事件处理器。假设你在写一个画图程序。根据程序的不同状态,鼠标按下可能开始画线,或者可能是选择一个对象。当使用者切换模式,你可以切换事件处理器。不同的类,不同的事件处理器,事件的处理依赖于程序的状态。
最后,使用事件,你可以在一个事件上挂多个事件处理器。再想象刚才的画图程序。你可能有多个事件处理器跟鼠标事件挂钩。第一个是具体的行为。第二个是更新状态栏或者更新不同命令的可用性。多个行为可以在同一个事件响应。
当你基类有一个函数处理一个事件,重载是更好的方法。这个更容易维护,s即使时间退役也更可能是正确的,并且更高效。保留事件处理器做其他用途。更倾向与重载基类的实现而不是附加事件处理器。
小结:
这个原则就是说 WPF 编程实现事件响应要选择重载虚函数的方式,而不是事件模式!
欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!
有关本书的其他章节翻译请[点击查看](/category/297763),转载请注明出处,尊重原创!
如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。
转载请在**文首**注明出处:[http://dsqiu.iteye.com/blog/2087024](/blog/2087024)
更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)
- 第一章 C# 语言习惯
- 原则1:使用 属性(Poperty)代替可直接访问的数据成员(Data Member)
- 原则2:偏爱 readonly 而不是 const
- 原则3:选择 is 或 as 而不是强制类型转换
- 原则4:使用条件特性(conditional attribute)代替 #if
- 原则5:总是提供 ToString()
- 原则6:理解几个不同相等概念的关系
- 原则7:明白 GetHashCode() 的陷阱
- 原则8:优先考虑查询语法(query syntax)而不是循环结构
- 原则9:在你的 API 中避免转换操作
- 原则10:使用默认参数减少函数的重载
- 原则11:理解小函数的魅力
- 第二章 .NET 资源管理
- 原则12:选择变量初始化语法(initializer)而不是赋值语句
- 原则13:使用恰当的方式对静态成员进行初始化
- 原则14:减少重复的初始化逻辑
- 原则15:使用 using 和 try/finally 清理资源
- 原则16:避免创建不需要的对象
- 原则17:实现标准的 Dispose 模式
- 原则17:实现标准的 Dispose 模式
- 原则18:值类型和引用类型的区别
- 原则19:确保0是值类型的一个有效状态
- 原则20:更倾向于使用不可变原子值类型
- 第三章 用 C# 表达设计
- 原则21:限制你的类型的可见性
- 原则22:选择定义并实现接口,而不是基类
- 原则23:理解接口方法和虚函数的区别
- 原则24:使用委托来表达回调
- 原则25:实现通知的事件模式
- 原则26:避免返回类的内部对象的引用
- 原则27:总是使你的类型可序列化
- 原则28:创建大粒度的网络服务 APIs
- 原则29:让接口支持协变和逆变
- 第四章 和框架一起工作
- 原则30:选择重载而不是事件处理器
- 原则31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 实现排序关系
- 原则32:避免 ICloneable
- 原则33:只有基类更新处理才使用 new 修饰符
- 原则34:避免定义在基类的方法的重写
- 原则35:理解 PLINQ 并行算法的实现
- 原则36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原则37:构造并行算法的异常考量
- 第五章 杂项讨论
- 原则38:理解动态(Dynamic)的利与弊
- 原则39:使用动态对泛型类型参数的运行时类型的利用
- 原则40:使用动态接收匿名类型参数