54

Possible Duplicate:
How to pass an event to a method?

Is it possible to pass an event as a parameter to a method?

For example, the following method subscribes to the event, does work, and unsubscribes from the event:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
        IEnumerable<TElement> elements,
        ??? elementEvent)
    where TEventArgs: EventArgs
{
    EventHandler<TEventArgs> handler = (sender, e) => { /* Handle an event */ };

    foreach (var element in elements)
    {
         // Subscribe somehow
         element.elementEvent += handler
    }

    // Do things

    foreach (var element in elements)
    {
         // Unsubscribe somehow
         element.elementEvent -= handler
    }
}

Client code:

var elements = new [] { new Button(), new Button() };
SubscribeDoAndUnsubscribe(elements, ??? /* e => e.Click */);

If it's not possible, how do I achieve the similar logic in other ways? Shall I pass pair of delegates for subscribe/unsubscribe methods?

Community
  • 1
  • 1
altso
  • 2,311
  • 4
  • 26
  • 40
  • Look here: http://stackoverflow.com/questions/8022406/passing-an-event-and-a-delegate-event-handler-into-a-generic-helper-method/8022448#8022448 – Felix K. Dec 06 '11 at 22:39

3 Answers3

55

You have in fact discovered that events are not "first class" in C#; you cannot pass around an event as data. You can pass around a delegate to a method associated with a receiver as a first-class object by making a delegate. You can pass around a reference to any variable as a (mostly) first-class object. (I say "mostly" because references to variables cannot be stored in fields, stored in arrays, and so on; they are highly restricted compared to other kinds of data.) You can pass around a type by obtaining its Type object and passing that around.

But there is no way to directly pass around as data an event, property, indexer, constructor or destructor associated with a particular instance. The best you can do is to make a delegate (or pair of delegates) out of a lambda, as you suggest. Or, obtain the reflection object associated with the event and pass that around, along with the instance.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    There is an exception to that rule: You CAN pass an event as a parameter to a method when the event you are passing is declared in the same class that is making this call. As in class A{ public event SomeDelegate YourEvent; CallMe(SomeDelegate theEvent){theEvent(this,args); } } – ILIA BROUDNO Jan 28 '17 at 01:41
  • @ILIABROUDNO: I don't see where in your little sample there `YourEvent` is ever passed to anything. – Eric Lippert Jan 28 '17 at 15:06
  • My bad. Put void in front of CallMe and then add another method to class A : void OtherMethod(){CallMe(YourEvent);} Now you are passing YourEvent as a parameter. – ILIA BROUDNO Jan 29 '17 at 06:33
  • 3
    You are not passing an event, you are passing a delegate. – Softlion Apr 02 '17 at 09:12
37

No, unfortunately not.

If you look at Reactive Extensions, that suffers from a similar problem. Three options they use (IIRC - it's been a while since I've looked):

  • Pass in the corresponding EventInfo and call it with reflection
  • Pass in the name of the event (and the target if necessary) and call it with reflection
  • Pass in delegates for subscription and unsubscription

The call in the latter case would be something like:

SubscribeAndDoUnsubscribe(elements,
                          handler => e.Click += handler,
                          handler => e.Click -= handler);

and the declaration would be:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
        IEnumerable<TElement> elements,
        Action<EventHandler<TEventArgs>> subscription,
        Action<EventHandler<TEventArgs>> unsubscription)
    where TEventArgs: EventArgs
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
4

You're trying to get around type safety, and you can't do so without using reflection. I'll show you an even simpler example of what you're trying to do.

void DoSomethingOnSomethingElse(T obj, Action method)
{
    obj.method();
}

C# doesn't work this way. How does the compiler know that all Ts have the method method? It doesn't, and can't. Similarly, not every TElement in your code will have an event Click for example.

It sounds like you just want to set a single use event handler on a set of objects. You can do this quite easily...

EventHandler handler = null;
handler = (s,e) => 
{
    DoSomething(e);
    var b = (Button) s;
    b.Click -= handler;
}

foreach (var button in buttons) 
{
    button.Click += handler;
}

This, obviously, only works with buttons, but as I write this, I see Jon Skeet has shown you a more general solution, so I'll end here.

Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164