Monday, October 1, 2007

Sender is an object because of Event Guidelines!!

The .NET Framework uses delegates extensively for event-handling tasks like a button click event exposed by a button object. That said, lets look at why in asp.net, all the web controls that expose an event all expose the sender argument as type object. Why is this sender argument not strongly typed to the control that fired it ?

For eg. Drop a Button Web Control on your webform and subscribe to its click event. Why is the event skeleton for the event, created by your IDE, declaring the sender as object ?

So, the sender is of type object but when the event is fired, the sender holds a reference to the object that fired the event, couldnt we just strongly type it from the start. As we already know what type it is Vs doing a cast later in the method and evaluating at runtime. The simple answer is NO! Lets take our button control as an example.

The reason we cannot declare the sender as type button even though we already know its a button control that subscribed to it, is that, the delegate used to handle this event has this specific delegate signature. (object sender, EventArgs e) ; you cant change it.
As the name translates to, a delegate outsources* the task of calling a method to someone else. Delegates basically allow methods to be passed as parameters. In our button class example, the delegate used is of type EventHandler.

public delegate void EventHandler(Object sender, EventArgs e);

This delegate as you have guessed already can be used to call any method with a matching signature, that is a void return type and two parameters. 1 an object parameter(sender) and 2. an EventArgs parameter(e). So, nothing matters now but that the subscribers to this event must have a matching method with this exact signature, that is the argument list must match. There is actually an exception to this rule, and variance in the signature to a certain extent is allowed when matching delegate methods with delegate signatures. To slightly touch the subject, Covariance enables delegate methods to be created that can be used by both classes and derived classes.

So taking from our previous example, imagine having a delegate with a return type as :

public delegate Button EventHandler(Object sender, EventArgs e);

now our subscribers can subscribe to this delegate and the return type could be a more specific class, that is a class that derives from Button. Because the method's return type is more specific than the delegate signature's return type, it can be implicitly converted. The method is therefore acceptable for use as a delegate.

Contravariance on the other hand is When a delegate method signature has one or more parameters of types that are derived from the types of the method parameters, that method is said to be contravariant. As the delegate method signature parameters are more specific than the method parameters, they can be implicitly converted when passed to the handler method. so while Covariance allowed us to have a more specific return type for our delegates signature, Contravariance on the other hand allows us to have the same thing on the argument list.

So it is possible to define a different type for our sender argument, even though the delegate has the sender marked as object ?

Well, contravariance in this way did not work for us. It only works on concrete definitions of a class. Although they are sometimes used interchangeably, a class and an object are different things. A class defines a type of object, but it is not an object itself. An object is a concrete entity based on a class, and is sometimes called an instance of a class. So this is probably why we were unable to match signatures. If the delegate had the sender argument as type :

public delegate void CustomEventHandler(Button sender, EventArgs e);

Then contravariance would have worked in our previous example that used the EventHandler delegate if we had the argument of our method, for eg. being either a Button or a Class that derives Button Vs it being an object.

The only way to change it is within the class that declared the delegate, by changing the delegate arguments! So a good question now is, why do all the controls expose the sender as object ? Why couldnt they declare the delegates as a strongly typed class of the object in question. In our button example why is the button class using a delegate with the sender parameter as type object then.

This is what the documentation has to say in this regard :
The .NET Framework guidelines indicate that the delegate type used for an event should take two parameters: an object source parameter indicating the source of the event, and an event-specific parameter that encapsulates any additional information about the event. The event-specific parameter should derive from the EventArgs class. For events that do not use any additional information, the .NET Framework provides the EventHandler class.
So, you have it now. Its a guideline. It does not effect my life if the sender is an object type or strongly typed. I guess ultimately in the end its a matter of good design and a coding convention used by microsoft, which most of us will eventually adopt :-)

Another valid question you might want to ask yourselves is, why does the guide line suggest you to have a second parameter, an event-specific patameter that is of type EventArgs or dervies from EventArgs, even in the case when you dont have any EventArgs argument you want to pass. This has already been answered here :
http://blogs.msdn.com/kcwalina/archive/2005/11/18/WhyEventArgs.aspx

No comments:

Post a Comment