12

I have the following class, which has one public event called LengthChanged:

class Dimension
{
    public int Length
    {
        get
        {
            return this.length;
        }
        set
        {
            if (this.length != value)
            {
                this.length = value;
                this.OnLengthChanged ();
            }
    }

    protected virtual void OnLengthChanged()
    {
        var handler = this.LengthChanged;
        if (handler != null)
        {
            handler (this, System.EventArgs.Empty);
        }
    }

    public event System.EventHandler LengthChanged;

    private int length;
}

I would like to be able to register/unregister handlers for this event in a method called Observer, which does not know anything about the Dimension class. I have come up with two scenarios, none of which are really satisfying:

  1. Define an interface ILengthChanged with the LengthChanged event, then make sure Dimension implements ILengthChanged. Then I have to provide one implementation of the Observer method for every interface I define. This by no way generic enough. I'd really want to be able to simply pass in a reference to a System.EventHandler event.

  2. Use System.Action<System.EventHandler> callbacks for registering and unregistering the event handler in the Observer method, just like that:

    class Foo
    {
        public void Observer(System.Action<System.EventHandler> register,
                             System.Action<System.EventHandler> unregister)
        {
            register (this.MyEventHandler);

            // keep track of the unregister callback, so that we can unregister
            // our event handler later on, if needed...
        }

        private void MyEventHandler(object sender, System.EventArgs e)
        {
            ...
        }
    }

which would then be invoked like this:

Foo foo = ...;
Dimension dim = ...;

foo.Observer (x => dim.LengthChanged += x, x => dim.LengthChanged -= x);

and which, when executed, will indeed end up wiring the LengthChanged event with the internal event handler MyEventHandler. But this is not very elegant. I would have loved to be able to write this instead:

Foo foo = ...;
Dimension dim = ...;

foo.Observer (dim.LengthChanged);

but I've no idea how this could be achieved. Maybe I am missing something really obvious here? I guess that some dynamic magic could do the trick, somehow, but this would not enforce compile-time type checking: I don't want the users of Observer to pass in references to events which do not satisfy the System.EventHandler event signature.

Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108

3 Answers3

13

Unfortunately there isn't really a way of doing this. Events aren't first class citizens in .NET in general - although F# tries to promote them there.

Either pass in the subscribe/unsubscribe delegate or using a string indicating the name of the event. (The latter is often shorter, but obviously less safe at compile-time.)

Those are the approaches which Reactive Extensions takes - if there were a cleaner way of doing it, I'm sure they would be using that :(

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • If people like Erik Meijer were not able to come up with cleaner solutions, I suspect that indeed, there is no other way to do it. That's sad... Thank you for your quick reply, Jon. – Pierre Arnaud Jan 21 '11 at 09:09
3

You can create a custom accessor.

public event EventHandler NewEvent
{
    add { Dimension.LengthChanged += value; }
    remove { Dimension.LengthChanged -= value; }
}

Please see the documentation.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Sajesh S F
  • 39
  • 1
2

Event is not supposed to be passed into another method. However, you can pass delegate into another method. Perhaps, what you are looking for are just a simple public delegate instead of event.

If you change your event to this

public System.EventHandler LengthChanged; 

You can simply pass the LengthChanged to Observer like this

Foo foo = ...;
Dimension dim = ...;
foo.Observer (dim.LengthChanged); 
Harvey Kwok
  • 11,713
  • 6
  • 37
  • 59
  • Thank you for your suggestion. No, I cannot pass the delegate instead of the event; the event might implement the `add` and `remove` setters and really has to be defined as a full fledged C# `event`. – Pierre Arnaud Jan 21 '11 at 09:07
  • @Pierre I see.. in that case, what I would do is to declare a delegate wrapper class. The constructor of that class takes a delegate. The class has virtual AddHandler() and virtual RemoveHandler(). It also has a Invoke() method to call the delegate. To make it look more elegant, you can define operator +=, -=, + and -, calling your AddHandler and RemoveHandler. Replace your Dimension's LengthChanged event by this wrapper class. For the case you need "add" and "remove" setters, just override that wrapper class's AddHandler and RemoveHandler methods. Just my 2 cents. I have done similar. – Harvey Kwok Jan 21 '11 at 17:17
  • nice solution... I'll have to think it over. +1 for this nice hack. – Pierre Arnaud Jan 22 '11 at 21:20