Item 35: Prefer Overrides to Event Handlers
优先考虑重写方法而不是事件句柄
Many .NET classes provide two different ways to handle events from the system. You can attach an event handler, or you can override a virtual function in the base class. Why provide two ways of doing the same thing? Because different situations call for different methods, that's why. Inside derived classes, you should always override the virtual function. Limit your use of the event handlers to responding to events in unrelated objects.
很多.Net的类提供了2种不同的方式来处理来自系统的事件。你可以绑定事件句柄,或者重写基类里面的虚方法。为什么对于同一件事提供了2种方式呢?因为不同的环境需要不同的方法,这就是原因。在派生类内部,你应该总是重写虚方法。在无关的对象中,使用事件句柄来对事件做出响应。
You write a nifty Windows application that needs to respond to mouse down events. In your form class, you can choose to override the OnMouseDown() method:
你编写了一个好玩的Windows应用程序,需要对鼠标按下事件做出响应。在你的form类里面,你可以选择重写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 );
- }
- }
Or, you could attach an event handler:
或者,你也可以绑定一个事件句柄:
- 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.
- }
- }
- }
The first method is preferred. If an event handler throws an exception, no other handlers in the chain for that event are called (see Item 21). Some other ill-formed code prevents the system from calling your event handler. By overriding the protected virtual function, your handler gets called first. The base class version of the virtual function is responsible for calling any event handlers attached to the particular event. That means that if you want the event handlers called (and you almost always do), you must call the base class. In some rare cases, you will want to replace the default behavior instead of calling the base class version so that none of the event handlers gets called. You can't guarantee that all the event handlers will be called because some ill-formed event handler might throw an exception, but you can guarantee that your derived class's behavior is correct.
第一个方法更好一些。如果一个事件句柄抛出异常的话,那么事件链上的其它句柄也不会被调用了(见Item21)。一些其它恶意代码就可以阻止系统调用你的事件句柄了。通过重写受保护的虚方法,首先你的句柄就被调用了。基类版本的虚方法有责任调用绑定到特定事件上的句柄。那就意味着,如果你希望句柄被调用(你总是这样希望的),你就必须调用基类。几乎没有这样的情况:你希望替换掉默认的行为,不再调用基类的版本,那样的话,就没有哪个事件句柄会被调用了。你不能保证所有的事件句柄会被调用,因为一些有问题的事件句柄可能抛出异常,但是你可以保证你的派生类的行为是正确的。
Using the override is more efficient than attaching the event handler. I showed you in Item 22 how the System.Windows.Forms.Control class uses a sophisticated collection mechanism to store event handlers and map the appropriate handler to a particular event. The event-handling mechanism takes more work for the processor because it must examine the event to see if any event handlers have been attached. If so, it must iterate the entire invocation list. Each method in the event invocation list must be called. Determining whether there are event handlers and iterating each at runtime takes more execution time than invoking one virtual function.
使用重写比绑定事件句柄要高效。在Item22里面,我向你展示了,System.Windows.Forms.Control类如何使用复杂的集合机制来存储事件句柄,将恰当的句柄映射给特定的事件。该事件处理机制占用了处理器更多的工作,因为它必须检查该事件来确认是否每个事件句柄都已经被绑定了。如果是,它必须迭代整个调用列表。在事件调用列表中的每个方法都必须被调用。要判断是否有事件句柄,在运行时对每一个进行迭代,与调用虚方法相比,花费更多的执行时间。
If that's not enough for you, examine the first listing in this item again. Which is clearer? Overriding a virtual function has one function to examine and modify if you need to maintain the form. The event mechanism has two points to maintain: the event handler function and the code that wires up the event. Either of these could be the point of failure. One function is simpler.
如果对你来说那还不够的话,那么再次检查本条款的第一个列表。哪个更简洁清晰呢?当你需要对form进行维护的时候,重写虚方法有一个方法需要检查和修改,而事件机制有2个方面要维护:事件句柄方法;处理事件的代码。其中的任何一个都可能是导致失败的地方。一个方法当然更简单些。
Okay, I've been giving all these reasons to use the overrides and not use the event handlers. The .NET Framework designers must have added events for a reason, right? Of course they did. Like the rest of us, they're too busy to write code nobody uses. The overrides are for derived classes. Every other class must use the event mechanism. For example, you often add a button click handler in a form. The event is generated by the button, but the form object handles the event. You could define a custom button and override the click handler in that class, but that's way too much work to handle one event. It only moves the problem to your own class anyway: Somehow, your custom button must communicate to the form that the button was clicked. The obvious way to handle that is to create an event. So, in the end, you have created a new class to send an event to the form class. It would be simpler to just attach the form's event handler to the form in the first place. That's why the .NET Framework designers put those events in the forms in the first place.
好啦,对于使用虚方法而不是事件句柄,我已经给出了所有的原因。.Net框架的设计者必须为某个原因添加事件,对不对?当然了。和我们其他人一样,他们也很忙,不会编写没有人会使用的代码。重写的虚方法是为派生类设计的;其它的类必须使用事件机制。例如,你经常在一个form里面加入一个button的点击事件。事件由按钮生成,但是form对象处理该事件。你可以定义一个自己的按钮,在类里面重写click句柄,但是那样做,对于处理一个事件来说是太多的工作。它仅仅是将问题移到了你自己的类里面:无论如何,你自定义的按钮必须和按钮被点击所在的那个窗体进行通信。处理这种情况的最显然的方式就是创建一个事件。因此,最后,你创建一个新类来向form类发送事件。那么,一开始就将form的事件句柄绑定到form上会简单点。这就是为什么.Net框架的设计者首先将这些事件放在了form中。
Another reason for the event mechanism is that events are wired up at runtime. You have more flexibility using events. You can wire up different event handlers, depending on the circumstances of the program. Suppose that you write a drawing program. Depending on the state of the program, a mouse down might start drawing a line, or it might select an object. When the user switches modes, you can switch event handlers. Different classes, with different event handlers, handle the event depending on the state of the application.
事件机制的另一个原因是:在运行时,事件是被包装起来的。使用事件你有更多的灵活性。你可以依据程序的环境,包装不同的事件句柄。假设你编写了一个绘画程序。根据程序的状态,鼠标按下事件,应该开始绘制一条直线,或者选中一个对象。当用户切换模式的时候,你可以切换事件句柄。不同的类,有不同的事件句柄,如何处理事件,取决于应用程序的状态。
Finally, with events, you can hook up multiple event handlers to the same event. Imagine the same drawing program again. You might have multiple event handlers hooked up on the MouseDown event. The first would perform the particular action. The second might update the status bar or update the accessibility of different commands. Multiple actions can take place in response to the same event.
最后,你可以通过事件,将多个事件句柄挂钩到同一个事件上。我们再来看看刚才的绘画程序。在MouseDown事件上,你可能有多个事件处理钩子。第一个可能将执行特定的动作;第二个可能是更新状态条或者更新不同命令的可访问性。多个动作可以在同一个事件上发生。
When you have one function that handles one event in a derived class, the override is the better approach. It is easier to maintain, more likely to be correct over time, and more efficient. Reserve the event handlers for other uses. Prefer overriding the base class implementation to attaching an event handler。
当你在派生类里面只有一个方法来处理一个事件的时候,重写虚方法是更好的选择。它更容易维护,随着时间的流失,更容易保持正确性,更有效率。保留事件句柄是为了其它用处。优先考虑重写基类的实现,而不是事件句柄。