Introduction
Let me start by saying that this idea isn't new, but I constantly find myself using this code and I couldn't find a decent example on CodeProject, so here it is for anyone that wants/needs it. Also, as the principle is very simple, this is not going to be the longest article in history!
Microsoft introduced a few generic delegates that are commonly used in .NET 2.0 such as EventHandler<TEventArgs>
, but for some reason didn't include a generic version of EventArgs
itself.
We are all familiar with event arguments that get passed by objects in our code. They are a very useful way of sending values between classes. The problem is that every time we want to pass a different type of value, we need to create a new class that derives from System.EventArgs
(or a class that already does this).
Normal EventArgs
For example, if we wanted to pass an int
from a class in our code, we would need to write a class such as this:
public class IntEventArgs : EventArgs
{
public IntEventArgs(int value)
{
Value = value;
}
public int Value { get; private set; }
}
OK, so this is no problem and very trivial. If we need a similar class, but want to pass a string
, then we have to write another class that is essentially the same, but using string
in place of int
. This is a waste of our time and exactly what Generics are there for!
Generic EventArgs
The same class using Generics looks like this:
EventArgs<T>
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
Value = value;
}
public T Value { get; private set; }
}
Now we can pas in any type we like in place of the int
, without needing to write another class ever!
In the real world, there are many types of event arguments that could be suitable for applying Generics to. Because all our generic versions will have the same property, I use a very simple interface that all my generic event argument classes implement.
IEventArgs<T>
public interface IEventArgs<T>
{
T Value { get; }
}
So the declaration of the class above becomes:
public class EventArgs<T> : EventArgs, IEventArgs<T>
{
}
The next obvious candidate is System.ComponentModel.CancelEventArgs
. The generic version of this derives from CancelEventArgs
and IEventArgs
. This class is only slightly complicated by the need for more than one constructor and calls to the base constructor.
CancelEventArgs<T>
public class CancelEventArgs<T> : CancelEventArgs, IEventArgs<t>
{
public CancelEventArgs(T value)
: this(value, false)
{ }
public CancelEventArgs(T value, bool cancel)
: base(cancel)
{
Value = value;
}
public T Value { get; private set; }
}
I find the most common place I use cancel event arguments are in XxxChanging
events, where it is useful to provide the current value, the new value, and the option to cancel, all in one event argument class instance. Instead of using a different generic EventArgs for this, it is just as easy to create a generic ChangingData
class and use that as the type for our generic CancelEventArgs.
ChangingData<T>
public class ChangingData<T>
{
public ChangingData(T oldData, T newData)
{
OldData = oldData;
NewData = newData;
}
public T OldData { get; private set; }
public T NewData { get; private set; }
}
In Use
In use, an event declaration might look like this if the value that was changing was an int
:
public event EventHandler<CancelEventArgs<ChangingData<int>>> ValueChanging;
That's a generic event handler with a generic cancel event argument whose type is a generic class! The actual Value
property implementation in such a class would probably look similar to this:
public int Value
{
get { return _Value; }
set
{
if (_Value != value)
{
CancelEventArgs<ChangingData<int>> e =
new CancelEventArgs<ChangingData<int>>(
new ChangingData<int>(_Value, value));
OnValueChanging(e);
if (!e.Cancel)
{
_Value = value;
OnValueChanged(new EventArgs<int>(_Value));
}
}
}
}
In fact, this is pasted straight out of the source code attached.
Conclusion
So that's it, a very simple way to reduce the amount of code we have to write when creating events, by using Generics. The attached source code has fully commented versions of all the classes above as well as a working demonstration of them in use. I hope you find them useful as I do.
History
- V1. 5th October 2009: Initial version.
- V2. 5th October 2009: Minor fixes.
- V3. 5th October 2009: Fixed multiple <>.
- V4. 5th October 2009: Fixed more <> - damn MS for picking these for Generics!
- V5. 5th October 2009: And one more!