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

Effective C# 原则35:选择重写函数而不是使用事件句柄
Item 35: Prefer Overrides to Event Handlers

很多.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事件上挂接了多个事件句柄。第一个可能是完成详细的功能,第二个可能是更新状态条或者更新一些可访问的不同命令。不同的行为可以在同一事件上响应。

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

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.

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:

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.

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.

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.

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.

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.

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.
 
   

   

原文地址:https://www.cnblogs.com/WuCountry/p/688983.html