Chris Marasti-Georg
Chris Marasti-Georg

Reputation: 34650

.NET EventHandlers - Generic or no?

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?


EDIT: The reason for this post is that I just re-created this in a new project, and wanted to make sure it was ok. Actually, I was re-creating it as I posted. I found that there is a generic EventHandler<TEventArgs>, so you don't need to create the generic delegate, but you still need the generic EventArgs<T> class, because TEventArgs: EventArgs.
Another EDIT: One downside (to me) of the built-in solution is the extra verbosity:

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

Answers (9)

Ryan Lundy
Ryan Lundy

Reputation: 210160

To make generic event declaration easier, I created a couple of code snippets for it. To use them:

  • Copy the whole snippet.
  • Paste it in a text file (e.g. in Notepad).
  • Save the file with a .snippet extension.
  • Put the .snippet file in your appropriate snippet directory, such as:

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

ligaoren
ligaoren

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

Ilya Ryzhenkov
Ilya Ryzhenkov

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:

  1. You cannot add more properties to the event data without changing dependent code. You will have to change the delegate signature to provide more data to the event subscriber.
  2. Your data object is generic, but it is also "anonymous", and while reading the code you will have to decipher the "Item" property from usages. It should be named according to the data it provides.
  3. Using generics this way you can't make parallel hierarchy of EventArgs, when you have hierarchy of underlying (item) types. E.g. EventArgs<BaseType> is not base type for EventArgs<DerivedType>, even if BaseType is base for DerivedType.

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

dance2die
dance2die

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

Chuck
Chuck

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

Romain Verdier
Romain Verdier

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

mattlant
mattlant

Reputation: 15451

Since .NET 2.0

EventHandler<T>

has been implemented.

Upvotes: 2

David Walschots
David Walschots

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

swilliams
swilliams

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

Related Questions