Code maintenance tip, and why EventArgs are a good thing
…tap, tap, tap…
I’ve been coding away lately and ran into one of those frustrating situations where I needed to add several new arguments to a function with a parameter list already a mile long. That’s used in three places. And I wasn’t sure what parameters I really needed.
public class A {
public virtual object FooFunction (object foo, object bar, object baz, int foozle, int bazzle, int barrzle) {
}
public void FunctionThatCallsFooFunction(…) {
// call the function with a lot of parameters
FooFunction (foo, bar, baz, foozle, bazzle, barrzle);
}
}
public class B : A {
public overide object FooFunction (object foo, object bar, object baz, int foozle, int bazzle, int barrzle) {
}
}
public class C : A {
public overide object FooFunction (object foo, object bar, object baz, int foozle, int bazzle, int barrzle) {
// oops I need a FlibbertyGibbet and a WillOfAWhisp!
}
}
So when I had to add the second parameter, and was updating the function for the second time in the three places, I took a page out of the EventArgs book.
public class A {
public virtual object FooFunction (FunctionArgs args) {
}
public FunctionThatCallsFooFunction (…) {
// call the function with a lot of parameters
FooFunction (new FunctionArgs(foo, bar, baz, foozle, bazzle, barrzle));
}
}
public class B : A {
public overide object FooFunction (FunctionArgs args) {
}
}
public class C : A {
public overide object FooFunction (FunctionArgs args) {
// now I can get FlibbertyGibbet and a WillOfAWhisp from args.FlibbertyGibbet!
}
}
Where FunctionArgs is just a simple class:
public class FunctionArgs {
public FunctionArgs (object foo, object bar, object baz, int foozle, int bazzle, int barrzle) {
//…
}
public object Foo {
…
}
… and so on
}
Now I can just add new properties to FunctionArgs and not have to update all the other classes (B, C, D, etc).
You see this pattern with events/delegates in the .Net Framework. Deriving from EventArgs allows you to add new properties later on without updating all the places where the event is subscribed.
e.g. The delegate (function signature) for MouseEventArgs is
public delegate void MouseEventHandler(object sender, MouseEventArgs e);
There was nothing (besides the framework design guidelines)[1] that prevented us from writing the delegate this way:
public delegate void MouseEventHandler(object sender, MouseButtons button, int clicks, int x, int y, int delta);
…but if we wanted to do something like roll X & Y into a simple “Point location” parameter, we would have had to update all the places in the world that subscribe to mouse events.
In v2 of Windows Forms, we simply added a new MouseEventArgs.Location property. This allowed the old code to continue to work, and new code to use the handy helper property.
--
[1] Further notes from the Framework Design Guidelines
Consider using a derived class of System.EventArgs as the event argument, unless you are absolutely sure the event will never need to carry any data to the event-handling method, in which case you can use the System.EventArgs type directly.
If you define an event that takes an EventArgs instance instead of a derived class that you define, you cannot add data to the event in later versions. For that reason, it is preferable to create an empty derived class of EventArgs. This allows you add data to the event in later versions without introducing breaking changes.
Comments
- Anonymous
August 31, 2006
And the other great thing about using an "EventArgs" derivative as a parameter in events is that, thanks to co-(or is it contra-?)variance, you can point an event handler like "Click" (that only takes an EventArgs parameter) to a handler like "MouseDown" (that takes a MouseEventArgs parameter) and it just works!
Provided, of course, the MouseDown handler isn't using the extra fields in the derived class. - Anonymous
September 02, 2006
Ah yes. Occasionally handy. Here's more details:
http://msdn2.microsoft.com/en-us/library/ms173174.aspx - Anonymous
September 03, 2006
A good tip!
...and if you use Jetbrains ReSharper, you can do this very simply with the built-in "Extract Class from Parameters" refactoring. Just a happy customer...