Effective C#原则35:选择重写函数而不是使用事件句柄

很多.Net类提供了两种不同的方法来控制一些系统的事件。那就是,要么添 加一个事件句柄;要么重写基类的虚函数。为什么要提供两个方法来完成同样的 事情呢?其实很简单,那就是因为不同的情况下要调用为的方法。在派生类的内 部,你应该总是重写虚函数。而对于你的用户,则应该限制他们只使用句柄来响 应一些不相关的对象上的事件。

例如你很了一个很不错的Windows应用程 序,它要响应鼠标点下的事件。在你的窗体类中,你可以选择重写OnMouseDown ()方法:

public class MyForm : Form
{
 // Other code elided.
 protected override void OnMouseDown(
   MouseEventArgs e )
 {
  try {
   HandleMouseDown( e );
  } catch ( Exception e1 )
  {
   // add specific error handling here.
  }
  // *almost always* call base class to let
  // other event handlers process message.
  // Users of your class expect it.
   base.OnMouseDown( e );
 }
}

或者你可以添加一个 事件句柄:

public class MyForm : Form
{
 // Other code elided.
 public MyForm( )
 {
   this.MouseDown += new
   MouseEventHandler( this.MouseDownHandler );
 }
 private void MouseDownHandler( object sender,
  MouseEventArgs e )
  {
  try {
   HandleMouseDown( e );
  } catch ( Exception e1 )
  {
   // add specific error handling here.
  }
 }
}

前面一些方法要好一些,如 果在事件链上有一个句柄抛出了一个异常,那么其它的句柄都不会再被调用(参 见原则21)。一些“病态”的代码会阻止系统调用事件上的句柄。通 过重写受保护的虚函数,你的控制句柄会就先执行。基类上的虚函数有责任调用 详细事件上的所有添加的句柄。这就是说,如果你希望事件上的句柄被调用(而 且这是你最想完成的),你就必须调用基类。而在一些罕见的类中,你希望取代 基类中的默认事件行为,这样可以让事件上的句柄都不被执行。你不去保证所所 的事件句柄都将被调用,那是因为一些“病态”事件句柄可能会引发 一些异常,但你可以保证你派生类的行为是正确的。

使用重载比添加事 件句柄更高效。我已经在原则22中告诉过你,System.Windows.Forms.Control类 是如何世故的使用任命机制来存储事件句柄,然后映射恰当的句柄到详细的事件 上。这种事件机制要花上更多的处理器时间,那是因为它必须检测事件,看它是 否有事件句柄添加在上面。如果有,它就必须迭代整个调用链表。方法链表中的 每个方法都必须调用。断定有哪些事件句柄在那里,还要对它们进行运行时迭代 ,这与只调用一个虚函数来说,要花上更多的执行时间。

如果这还不足 以让你决定使用重载,那就再看看这一原则一开始的链表。那一个更清楚?如果 重载虚函数,当你在维护这个窗体时,只有一个函数要检查和修改。而事件机制 则有两个地方要维护:一个就是事件句柄,另一就是事件句柄上的函数。任何一 个都可能出现失败。就一个函数更简单一些。

OK,我已经给出了所有要 求使用重载而不是事件句柄的原因。.Net框架的设计者必须要添加事件给某人, 对吗?当然是这样的。就你我们剩下的内容一个,他们太忙了而没时间写一些没 人使用的代码。重写只是为派生类提供的,其它类必须使用事件机制。例如,你 经常添加一个按钮点击事件到一个窗体上。事件是由按钮触发的,但是由窗体对 象处理着事件。你完全可以在这个类中定义一个用户的按钮,而且重写这个点击 句柄,但这对于只是处理一个事件来说花上了太多的代码。不管怎样,问题都是 交给你自己的类了:你自己定义的按钮还是在点击时必须与窗体进行通信。显然 应该用事件来处理。因此,最后,你只不过是创建了一个新类来向窗体发送事件 (译注:其实我们完全可以创建这个类不用发事件给窗体就可以完成回调的,只 是作者习惯的说什么好就一味的否定其它。但不管怎样,重写一个按钮来重载函 数确实不是很值。)。 相对前面一种方法,直接在窗体事件添加句柄要简单得多 。这也就是为什么.Net框架的设计者把事件放在窗体的最前面。

另一个 要使用事件的原因就是,事件是在运行时处理的。使用事件有更大的伸缩性。你 可以在一个事件上添加多个句柄,这取决于程序的实际环境。假设你写了一个绘 图程序,根据程序的状态,鼠标点下时应该画一条线,或者这它是要选择一个对 象。当用户切换功能模式时,你可以切换事件句柄。不同的类,有着不同的事件 句柄,而处理的事件则取决于应用程序的状态。

最后,对于事件,你可 以把多个事件句柄挂到同样的事件上。还是想象同样的绘图程序,你可能在 MouseDown事件上挂接了多个事件句柄。第一个可能是完成详细的功能,第二个 可能是更新状态条或者更新一些可访问的不同命令。不同的行为可以在同一事件 上响应。

当你有一个派生类中只有一个函数处理一个事件时,重载是最 好的方法。这更容易维护,今后也会更正确,而且更高效。而应该为其它用户保 留事件。因此,我们应该选择重写基类的实现而不是添加事件句柄。

返回教程目录

时间: 2024-08-01 03:18:17

Effective C#原则35:选择重写函数而不是使用事件句柄的相关文章

Effective C#原则19:选择定义和实现接口而不是继承

抽象类在类的继承中提供了一个常规的"祖先".一个接口描述 了一个可以被其它类型实现的原子级泛型功能.各有千秋,却也不尽相同.接口 是一种合约式设计:一个类型实现了某个接口的类型,就必须实现某些期望的方 法.抽象类则是为一个相关类的集合提供常规的抽象方法.这些都是老套的东西 了:它是这样的,继承就是说它是某物(is a,),而接口就是说它有某个功能 (behaves like.)! 这些陈词滥调已经说了好久了,因为它们提供了说明,同时 在两个结构上描述它们的不同:基类是描述对象是什么,接

Effective C#原则42:使用特性进行简单的反射

当你创建了一个与反射相关的系统时,你应该为你自己的类型,方法,以及 属性定义一些自己的特性,这样可以让它们更容易的被访问.自定义的特性标示 了你想让这些方法在运行时如何被使用.特性可以测试一些目标对象上的属性. 测试这些属性可以最小化因为反射时可能而产生的类型错误. 假设你须 要创建一个机制,用于在运行时的软件上添加一个菜单条目到一个命令句柄上.这个须要很简单:放一个程序集到目录里,然后程序可以自己发现关于它的一些 新菜单条目以及新的菜单命令.这是利用反射可以完成的最好的工作之一:你的 主程序须

Effective C#原则22:用事件定义对外接口

可以用事件给你的类型定义一些外部接口.事件是基于委托的,因为委托可 以提供类型安全的函数签名到事件句柄上.加上大多数委托的例子都是使用事件 来说明的,以至于开发人员一开始都认为委托与事件是一回事.在原则21里,我 已经展示了一些不在事件上使用委托的例子.在你的类型与其它多个客户进行通 信时,为了完成它们的行为,你必须引发事件. 一个简单的例子,你正 在做一个日志类,就像一个信息发布机一样在应用程序里发布所有的消息.它接 受所有从程序源发布的消息,并且把这些消息发布到感兴趣的听众那里.这些听 众可

link中的函数知道了它的地址可以复制么?如何调用成员函数而不经过实例化?

问题描述 link中的函数知道了它的地址可以复制么?如何调用成员函数而不经过实例化? link中的函数知道了它的地址可以复制么?如何调用成员函数而不经过实例化? 解决方案 没实力化,成员就没有分配空间,也没有地址,你怎么用 解决方案二: 复制啥,函数还复制,直接用就是了

c++-C++根据代码行号或者函数名选择执行函数

问题描述 C++根据代码行号或者函数名选择执行函数 C++根据代码行号或者函数名选择执行函数,输入函数名自动循环执行指定的函数,咨询具体的实现方法 解决方案 你需要一个函数地址和函数名的对照数组,然后在程序运行的时候根据你的函数名查找对应的函数执行,然后就可以执行了. 解决方案二: 日志中自动记录所在函数名.文件名.行号c语言输出文件名函数名和行号python 获取当前函数名和行号 解决方案三: 可以参考qt的元对象 实现方式 解决方案四: 动态库吗?导出成c的动态库,然后采用loadlibra

JavaScript为事件句柄绑定监听函数实例详解_javascript技巧

本文实例讲述了JavaScript为事件句柄绑定监听函数的方法.分享给大家供大家参考,具体如下: 在JavaScript中为Dom元素绑定事件监听函数是一件非常常见的事情,但这里也有许多的Bug.各种浏览器对于事件绑定都提供了很多方法,但可靠的只有3中: 1.传统的绑定方法: elem.onclick = function( event ){ alert(event.type + 'this.innerHTML'); }; a.传统的绑定方法,非常简单稳定,函数体内的this指向的也是指向正在处

js使用函数绑定技术改变事件处理程序的作用域_javascript技巧

第一种,也是 最常见的,就是直接在html标签里面通过指定事件处理程序同名的HTML属性来注册事件,代码如下: 复制代码 代码如下: function eventHandler() { alert("当前作用域是 input 元素本身"); } <input type="button" value="单击我" onclick="eventHandler(this)"/> 第二种方式就是将一个函数赋值给一个事件处理程

十进制数-用vcmfc编写一个函数 在发送按钮事件下调用这个函数 具体见正文

问题描述 用vcmfc编写一个函数 在发送按钮事件下调用这个函数 具体见正文 该函数需将BYTE编辑框变量十进制数转化为16进制数,在按钮事件下调用该函数 进行转化.我是新手 求指导 刚接触vc思维上有点转不过来 解决方案 大概的思路char * input= ""00FF0123D5..."";char* out2=new char[strlen(input/2];int count=0;for(char *s=input;strlen(s)>0;s+=2)

vs2008 c# 实现radiobutton 的focus()函数不要触发click事件

问题描述 vs2008c#实现radiobutton的focus()函数不要触发click事件,该怎么写wndproc()这个函数 解决方案 解决方案二:focus本身就是一个事件啊,可以用来做事情.