Reputation: 34650
Every time I start in deep in a C# project, I end up with lots of events that really just need to pass a single item. I stick with the EventHandler
/EventArgs
practice, but what I like to do is have something like:
public delegate void EventHandler<T>(object src, EventArgs<T> args);
public class EventArgs<T>: EventArgs {
private T item;
public EventArgs(T item) {
this.item = item;
}
public T Item {
get { return item; }
}
}
Later, I can have my
public event EventHandler<Foo> FooChanged;
public event EventHandler<Bar> BarChanged;
However, it seems that the standard for .NET is to create a new delegate and EventArgs
subclass for each type of event. Is there something wrong with my generic approach?
EventHandler<TEventArgs>
, so you don't need to create the generic delegate, but you still need the generic EventArgs<T>
class, because TEventArgs: EventArgs
.
public event EventHandler<EventArgs<Foo>> FooChanged;
vs.
public event EventHandler<Foo> FooChanged;
It can be a pain for clients to register for your events though, because the System namespace is imported by default, so they have to manually seek out your namespace, even with a fancy tool like Resharper... Anyone have any ideas pertaining to that?
Upvotes: 24
Views: 24215
Reputation: 210160
To make generic event declaration easier, I created a couple of code snippets for it. To use them:
Visual Studio 2008\Code Snippets\Visual C#\My Code Snippets
Here's one that uses a custom EventArgs class with one property:
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Generic event with one type/argument.</Title>
<Shortcut>ev1Generic</Shortcut>
<Description>Code snippet for event handler and On method</Description>
<Author>Ryan Lundy</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>Type of the property in the EventArgs subclass.</ToolTip>
<Default>propertyType</Default>
</Literal>
<Literal>
<ID>argName</ID>
<ToolTip>Name of the argument in the EventArgs subclass constructor.</ToolTip>
<Default>propertyName</Default>
</Literal>
<Literal>
<ID>propertyName</ID>
<ToolTip>Name of the property in the EventArgs subclass.</ToolTip>
<Default>PropertyName</Default>
</Literal>
<Literal>
<ID>eventName</ID>
<ToolTip>Name of the event</ToolTip>
<Default>NameOfEvent</Default>
</Literal>
</Declarations>
<Code Language="CSharp"><![CDATA[public class $eventName$EventArgs : System.EventArgs
{
public $eventName$EventArgs($type$ $argName$)
{
this.$propertyName$ = $argName$;
}
public $type$ $propertyName$ { get; private set; }
}
public event EventHandler<$eventName$EventArgs> $eventName$;
protected virtual void On$eventName$($eventName$EventArgs e)
{
var handler = $eventName$;
if (handler != null)
handler(this, e);
}]]>
</Code>
<Imports>
<Import>
<Namespace>System</Namespace>
</Import>
</Imports>
</Snippet>
</CodeSnippet>
</CodeSnippets>
And here's one that has two properties:
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Generic event with two types/arguments.</Title>
<Shortcut>ev2Generic</Shortcut>
<Description>Code snippet for event handler and On method</Description>
<Author>Ryan Lundy</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type1</ID>
<ToolTip>Type of the first property in the EventArgs subclass.</ToolTip>
<Default>propertyType1</Default>
</Literal>
<Literal>
<ID>arg1Name</ID>
<ToolTip>Name of the first argument in the EventArgs subclass constructor.</ToolTip>
<Default>property1Name</Default>
</Literal>
<Literal>
<ID>property1Name</ID>
<ToolTip>Name of the first property in the EventArgs subclass.</ToolTip>
<Default>Property1Name</Default>
</Literal>
<Literal>
<ID>type2</ID>
<ToolTip>Type of the second property in the EventArgs subclass.</ToolTip>
<Default>propertyType1</Default>
</Literal>
<Literal>
<ID>arg2Name</ID>
<ToolTip>Name of the second argument in the EventArgs subclass constructor.</ToolTip>
<Default>property1Name</Default>
</Literal>
<Literal>
<ID>property2Name</ID>
<ToolTip>Name of the second property in the EventArgs subclass.</ToolTip>
<Default>Property2Name</Default>
</Literal>
<Literal>
<ID>eventName</ID>
<ToolTip>Name of the event</ToolTip>
<Default>NameOfEvent</Default>
</Literal>
</Declarations>
<Code Language="CSharp">
<![CDATA[public class $eventName$EventArgs : System.EventArgs
{
public $eventName$EventArgs($type1$ $arg1Name$, $type2$ $arg2Name$)
{
this.$property1Name$ = $arg1Name$;
this.$property2Name$ = $arg2Name$;
}
public $type1$ $property1Name$ { get; private set; }
public $type2$ $property2Name$ { get; private set; }
}
public event EventHandler<$eventName$EventArgs> $eventName$;
protected virtual void On$eventName$($eventName$EventArgs e)
{
var handler = $eventName$;
if (handler != null)
handler(this, e);
}]]>
</Code>
<Imports>
<Import>
<Namespace>System</Namespace>
</Import>
</Imports>
</Snippet>
</CodeSnippet>
</CodeSnippets>
You can follow the pattern to create them with as many properties as you like.
Upvotes: 9
Reputation: 1073
Use generic event handler instances
Before .NET Framework 2.0, in order to pass custom information to the event handler, a new delegate had to be declared that specified a class derived from the System.EventArgs class. This is no longer true in .NET
Framework 2.0, which introduced the System.EventHandler<T>) delegate. This generic delegate allows any class derived from EventArgs to be used with the event handler.
Upvotes: 1
Reputation: 12142
Delegate of the following form has been added since .NET Framework 2.0
public delegate void EventHandler<TArgs>(object sender, TArgs args) where TArgs : EventArgs
You approach goes a bit further, since you provide out-of-the-box implementation for EventArgs with single data item, but it lacks several properties of the original idea:
So, I think it is better to use generic EventHandler<T>, but still have custom EventArgs classes, organized according to the requirements of the data model. With Visual Studio and extensions like ReSharper, it is only a matter of few commands to create new class like that.
Upvotes: 28
Reputation: 36915
You can find Generic EventHandler on MSDN http://msdn.microsoft.com/en-us/library/db0etb8x.aspx
I have been using generic EventHandler extensively and was able to prevent so-called "Explosion of Types(Classes)" Project was kept smaller and easier to navigate around.
Coming up with a new intuitive a delegate for non-generic EventHandler delegate is painful and overlap with existing types Appending "*EventHandler" to new delegate name does not help much in my opinion
Upvotes: 2
Reputation: 8272
I do believe that the recent versions of .NET have just such an event handler defined in them. That's a big thumbs up as far as I'm concerned.
/EDIT
Didn't get the distinction there originally. As long as you are passing back a class that inherits from EventArgs, which you are, I don't see a problem. I would be concerned if you weren't wrapping the resultfor maintainability reasons. I still say it looks good to me.
Upvotes: 1
Reputation: 12971
The first time I saw this little pattern, I was using Composite UI Application block, from MS Patterns & Practices group.
It doesn't throw any red flag to me ; in fact it is even a smart way of leveraging generics to follow the DRY rule.
Upvotes: 3
Reputation: 12660
This is the correct implementation. It has been added to the .NET Framework (mscorlib) since generics first came available (2.0).
For more on its usage and implementation see MSDN: http://msdn.microsoft.com/en-us/library/db0etb8x.aspx
Upvotes: 3
Reputation: 48920
No, I don't think this is the wrong approach. I think it's even recommended in the [fantastic] book Framework Design Guidelines. I do the same thing.
Upvotes: 7