1

If I have a class that raises an event, with (e.g.) FrobbingEventArgs, am I allowed to handle it with a method that takes EventArgs?

Here's some code:

class Program
{
   static void Main(string[] args)
   {
      Frobber frobber = new Frobber();
      frobber.Frobbing += FrobberOnFrobbing;
      frobber.Frob();
   }

   private static void FrobberOnFrobbing(object sender,
       EventArgs e)
   {
      // Do something interesting. Note that the parameter is 'EventArgs'.
   }
}

internal class Frobber
{
   public event EventHandler<FrobbingEventArgs> Frobbing;
   public event EventHandler<FrobbedEventArgs> Frobbed;

   public void Frob()
   {
      OnFrobbing();

      // Frob.

      OnFrobbed();
   }

   private void OnFrobbing()
   {
      var handler = Frobbing;
      if (handler != null)
         handler(this, new FrobbingEventArgs());
   }

   private void OnFrobbed()
   {
      var handler = Frobbed;
      if (handler != null)
         handler(this, new FrobbedEventArgs());
   }
}

internal class FrobbedEventArgs : EventArgs { }
internal class FrobbingEventArgs : EventArgs { }

The reason I ask is that ReSharper seems to have a problem with (what looks like) the equivalent in XAML, and I'm wondering if it's a bug in ReSharper, or a mistake in my understanding of C#.

Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380

3 Answers3

6

Maybe covariant's not the word

Close. The word you are looking for is contravariant. Conversion of a method group to a delegate type is covariant in return type and contravariant in formal parameter types.

Here's my blog article on the subject:

https://learn.microsoft.com/en-us/archive/blogs/ericlippert/covariance-and-contravariance-in-c-part-three-method-group-conversion-variance

if I have a class that raises an event, with (e.g.) FrobbingEventArgs, am I allowed to handle it with a method that takes EventArgs?

Well, you tried it and it worked, so clearly yes. For the justification in the specification see the section helpfully entitled "Method group conversions".

Toxantron
  • 2,218
  • 12
  • 23
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Eric, thanks for the correction -- changed "covariant" to "contravariant". Also thanks for the justification. – Roger Lipscombe May 14 '10 at 15:11
  • This is unproblematic and works fine because the method group conversion of the second code line in `Main` converts to the exact same constructed generic type. (Runtime type is equal to compiletime type.) So this also works with .NET 2.0. Just if someone comes here searching for _"contravariance"_, note the following (.NET 4.0): Don't make an event like `public event Action Frobbing;` because `Action` is contravariant (`in`). People could then add an `Action` or similar to the event. And that **won't** work if other subscribers use a different `Action`. – Jeppe Stig Nielsen Nov 12 '12 at 14:32
1

Yes you can but when you access the e parameter in the event handler you will only be able to access the members that belong to EventArgs base class unless you cast it as the derived type. I think the word is polymorphic rather than covariant and all classes in c# are pollymorphic, it is a language feature.

Ben Robinson
  • 21,601
  • 5
  • 62
  • 79
  • The fact that FrobbedEventArgs can be casted to EventArgs is polymorphism, but I think the fact that a delegate of type EventHandler can be added to a EventHandler event is covariance. – Niki May 14 '10 at 11:11
  • @nikie Actually, if you [look at `EventHandler`](http://msdn.microsoft.com/en-us/library/db0etb8x.aspx), you will see that it's **invariant**, not contravariant, in its generic parameter. It says `EventHandler`, not `EventHandler`. So in this case it's not possible to add an `EventHandler` to an `EventHandler` event like you say. So it's contravariance of the method group conversion which creates an `EventHandler` even if the static target method has "smaller" requirements to its argument. – Jeppe Stig Nielsen Nov 12 '12 at 15:16
  • @JeppeStigNielsen: The behavior you note is an unfortunate consequence of Microsoft's decision not to have delegates define a `Combine` method, but instead use `Delegate.Combine`. Given interfaces IFoo, IBar, and IBoth:IFoo,IBar, If delegate type `Action` defined its own combine method, it would be possible for `Action.Combine` to accept an `Action` and an `Action` and yield an `Action`. As it is, though, there's no way for `Delegate.Combine` to determine what type it could possibly use to combine an `Action` and an `Action`. – supercat Dec 08 '12 at 00:05
  • @supercat I agree, there's no way `Delegate.Combine(Delegate, Delegate)` can determine the type. They could solve it like you suggest. Another possibility is to make a _generic_ but still `static` combine medthod, `public static TDel Combine(TDel x, TDel y) where TDel : Delegate { /* use typeof(TDel) in here */ }`. In that case, the `+` in C# between two delegates of equal **compile-time** type `TDel` should "translate" to a call to the generic `Combine` method. – Jeppe Stig Nielsen Dec 08 '12 at 08:40
  • @JeppeStigNielsen: The type system cannot handle generic constraints of delegate types. Also, it's worth noting that while one it's possible to write a type-safe `CombineAction` method which, given two delegates whose signatures are compatible with `Action` an `Action` and `Action` will yield an `Action` which, when invoked, will run both actions in sequence, and a non-type-safe version that will work with any two delegates whose signatures would be compatible with `Action`, in neither case would the resulting delegate be compatible with the... – supercat Dec 10 '12 at 16:17
  • @JeppeStigNielsen: `Delegate.Remove` or `Delegate.GetIvocationList` methods. Frankly, I don't like the .net event pattern (I'd have rather seen an `EventList` structure type which would hold an `Object` that could either be `null`, an `EventHandler`, or an `EventHandler[]`, and either specify that unsubscription would use strict referential equality, or else have subscription return an object which, when `Dispose`d, would unsubscribe). For better or for worse, though, enough code has been written using `Delegtate.Combine`/`Remove` for subscriptions that it's not going away any time soon. – supercat Dec 10 '12 at 16:25
-1

Since events can only be invoked from within the class that declared them, your derived class cannot directly invoke events declared within the base class.

You can achieve what you want by creating a protected invoking method for the event. By calling this invoking method, your derived class can invoke the event.

For even more flexibility, the invoking method is often declared as virtual, which allows the derived class to override it. This allows the derived class to intercept the events that the base class is invoking, possibly doing its own processing of them.

You can do:

protected void OnFrobbing(EventArgs e) 
   { 
      var handler = Frobbing; 
      if (handler != null) 
         handler(this, new e); 
   } 

Or:

protected virtual void OnFrobbing(EventArgs e) 
   { 
      var handler = Frobbing; 
      if (handler != null) 
         handler(this, new e); 
   } 
Valko
  • 340
  • 2
  • 10