Thursday, June 19, 2008

A Rule A Day: DeclareEventHandlersCorrectly

In the previous post I have talked about InitializeReferenceTypeStaticFieldsInline rule. Today let us look at "Declare event handlers correctly" DeclareEventHandlersCorrectly rule (CA1009). The rule is triggered when you declare events differently from accepted .NET signature pattern:

public delegate void ActionEventHandler1(String first, String second);
public delegate void ActionEventHandler2(object sender);
public delegate bool ActionEventHandler3();
public class EventClass
    public event ActionEventHandler1 ActionCalled1; // fires CA1009
    public event ActionEventHandler2 ActionCalled2; // fires CA1009
    public event ActionEventHandler2 ActionCalled3; // fires CA1009

The recommended way to declare event handling delegate is as following

public delegate void ActionEventHandler(object sender, ActionEventArgs e);

The points that are different from the code violating the rule:

  • No return value (because with event handler returning value it is impossible to attach several handlers to the event)
  • First parameter is of type object and generally should represent the object that fires the event
  • Second parameter is of type EventArgs or derived from EventArgs and represents event-related data

Why it is important to declare event handlers in such way? I can think of several explanations

  • For consistency with .NET framework. Chances are you are using quite a lot of .NET library classes in your application, and dealing with events in different way just adds unneeded complexity to code maintenance
  • The proposed patterns is flexible enough to handle both change of state within event handlers and custom data to be passed to event handler in uniform manner

From my experience, the custom event handlers implementations that are different from .NET paradigm usually result from lack of knowledge or from the perception that it is "too much hassle to implement custom delegate & arguments". So when you see this violation occurring in the code written by your team, that's probably time for some short education session.

Here are some hints on how to implement .NET event pattern. In .NET 2.0 one can use templated event handler to avoid declaring custom delegates, for example

public delegate void ActionEventHandler(object sender, ActionEventArgs e);
public class EventClass
    // use templated EventHandler instead
    // public event ActionEventHandler1 ActionCalled;  
    public event EventHandler<ActionEventArgs> ActionCalled;

If you do not have any EventArgs to supply and would like to use event without arguments, the elegant way is to use static EventArgs.Empty:

public class EventClass
    public event EventHandler ActionCalled;
    public void PerformAction()        
        // use empty event 
        // ActionCalled(this, new EventArgs()); 
        ActionCalled(this, EventArgs.Empty);

Using event arguments class derived from EventArgs allows explicit specification of event handlers operation contract, for example

public class ActionEventArgs : EventArgs
    private bool _suceeded;
    public bool Suceeded
        get { return _succeeded; }
        set { _suceeded = value; }
    private readonly string _originatorName;
    public string OriginatorName
        get { return _originatorName; }
    public ActionEventArgs(string originatorName)
        originatorName = _originatorName;
        _suceeded = true;

This arguments implementation specifies that

  • Event hanlder is provided with input parameter originator name
  • Event handler may choose to change the event result (by default event handling is assumed to succeeded)
  • If several event handlers are attached, the previous handler result propagates to the next handler

Contrast that with

public delegate bool ActionHandler(string originatorName); 

To summarize, in order to prevent DeclareEventHandlersCorrectly violations, and generally make your life easier while implementing custom events

  • Conform with .NET event implementation pattern
  • Use EventHandler<T>
  • Use EventArgs.Empty
  • Implement custom event arguments to better verbalize event in/out data for the handlers

P.S. By the way, if you are reading this post you may also enjoy reading new David Kean's blog, where he also deals with rules and violations. Highly recommended!

No comments: