Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it a good idea to implement a C# event with a weak reference under the hood?

I have been wondering whether it would be worth implementing weak events (where they are appropriate) using something like the following (rough proof of concept code):

class Foo {

    private WeakEvent<EventArgs> _explodedEvent = new WeakEvent<EventArgs>();

    public event WeakEvent<EventArgs>.EventHandler Exploded {
        add { _explodedEvent += value; }
        remove { _explodedEvent -= value; }
    }

    private void OnExploded() {
        _explodedEvent.Invoke(this, EventArgs.Empty);
    }

    public void Explode() {
        OnExploded();
    }

}

Allowing other classes to subscribe and unsubscribe from events with the more conventional C# syntax whilst under the hood actually being implemented with weak references:

static void Main(string[] args) {
    var foo = new Foo();
    foo.Exploded += (sender, e) => Console.WriteLine("Exploded!");

    foo.Explode();
    foo.Explode();
    foo.Explode();

    Console.ReadKey();
}

Where the WeakEvent<TEventArgs> helper class is defined as follows:

public class WeakEvent<TEventArgs> where TEventArgs : EventArgs {

    public delegate void EventHandler(object sender, TEventArgs e);

    private List<WeakReference> _handlers = new List<WeakReference>();

    public void Invoke(object sender, TEventArgs e) {
        foreach (var handler in _handlers)
            ((EventHandler)handler.Target).Invoke(sender, e);
    }

    public static WeakEvent<TEventArgs> operator + (WeakEvent<TEventArgs> e, EventHandler handler) {
        e._handlers.Add(new WeakReference(handler));
        return e;
    }

    public static WeakEvent<TEventArgs> operator - (WeakEvent<TEventArgs> e, EventHandler handler) {
        e._handlers.RemoveAll(x => (EventHandler)x.Target == handler);
        return e;
    }

}

Is this a good approach? are there any undesirable side effects to this approach?

like image 727
Lea Hayes Avatar asked Sep 04 '15 20:09

Lea Hayes


1 Answers

That's a bad idea because:

  1. Your program starts to become non-deterministic because side-effects depend on the actions of the GC.
  2. GCHandles come at a performance cost.

See the linked answer. It's a 95% duplicate but not quite enough to close the question I think. I'll quote the most relevant parts:


There also is a semantic difference and non-determinism that would be caused by weak references. If you hook up () => LaunchMissiles() to some event you might find the missiles to be launched just sometimes. Other times the GC has already taken away the handler. This could be solved with dependent handles which introduce yet another level of complexity.

I personally find it rare that the strong referencing nature of events is a problem. Often, events are hooked up between objects that have the same or very similar lifetime. For example you can hook up events all you want in the context of an HTTP request in ASP.NET because everything will be eligible for collection when the request has ended. Any leaks are bounded in size and short lived.

like image 117
usr Avatar answered Oct 04 '22 02:10

usr