views:

2573

answers:

9

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?

+5  A: 

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.

swilliams
+1  A: 

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.

Chuck
Recent versions have generic delegate, but they don't have single-item EventArgs
Ilya Ryzhenkov
+2  A: 

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

David Walschots
+2  A: 

Since .NET 2.0

EventHandler<T>

has been implemented.

mattlant
+2  A: 

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.

Romain Verdier
+1  A: 

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

Sung Meister
+13  A: 

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.

Ilya Ryzhenkov
So, if you have a data model with 20 different classes, you would create an EventArgs for each of them?
Chris Marasti-Georg
@Chris Marasti-GeorgIt depends on number of different events and their respective data, not on model size. If all your classes fire simple PropertyChanging/PropertyChanged events, you will have one EventArgs inheritor with PropertyName data.
Ilya Ryzhenkov
Supposing a CRUD type service - you'd have 3 events per item type, ItemCreated, ItemUpdated, ItemDeleted. I'd think the first 2 should pass along a copy of the item, and the last, perhaps its ID. If the CRUD service can handle 20 item types, you're looking at 21 EventArgs subclasses.
Chris Marasti-Georg
Chris, for the CRUD use-case, I think the ORM or the other respective system should generate relevant EventArgs, or use generic one if appropriate. If you are arguing about specific scenario, consider editing your question to explain this.
Ilya Ryzhenkov
I am not arguing for a specific scenario - the stuff I'm currently working on actually just needs to send along a string, and I can't make myself create a StringEventArgs. The string contains an identifier, but even IdentifierEventArgs, or IdEventArgs, holding a string, seems overkill to me.
Chris Marasti-Georg
As far as I understand, you need just EntityEventArgs, which holds enough information to identify an entity the event occurs on.
Ilya Ryzhenkov
+1. Especially your point 3, as well as the principle of least surprise. Not sure I understand your point 1 though.
Joe
On the #1, when you have EventHandler<FooEventArgs> you can add as many properties to the FooEventArgs as you wish without altering consumers. If you have EntityEventHandler<Foo> with automatic EventArgs, you have to change delegate signature to add another property, hence change clients.
Ilya Ryzhenkov
hmm... I think I see your point. An event deals with Foo - if I merely pass an EventArgs<Foo>, and then later need to add something non-foo related, say, the time of day, I would have to create a new EventArgs subclass, breaking my listeners. Is this what you're getting at?
Chris Marasti-Georg
Yes, something like this. If you already have custom class, you can add time-stamp, transaction info, version info, cancel flag or whatever without dealing with every listener. Only those listeners, that care about new data should be updated.
Ilya Ryzhenkov
+5  A: 

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"&gt;
    <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>Kyralessa</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>
     </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"&gt;
  <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>Kyralessa</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>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

You can follow the pattern to create them with as many properties as you like.

Kyralessa
A: 

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.

ligaoren