views:

2281

answers:

4

UPDATE

I have combined various answers from here into a 'definitive' answer on a new question.

Original question

In my code I have an event publisher, which exists for the whole lifetime of the application (here reduced to bare essentials):

public class Publisher
{
    //ValueEventArgs<T> inherits from EventArgs
    public event EventHandler<ValueEventArgs<bool>> EnabledChanged; 
}

Because this publisher can be used all over the place, I was quite pleased with myself for creating this little helper class to avoid re-writing the handling code in all subscribers:

public static class Linker
{
    public static void Link(Publisher publisher, Control subscriber)
    {
         publisher.EnabledChanged += (s, e) => subscriber.Enabled = e.Value;
    }

    //(Non-lambda version, if you're not comfortable with lambdas)
    public static void Link(Publisher publisher, Control subscriber)
    {
         publisher.EnabledChanged +=
             delegate(object sender, ValueEventArgs<bool> e)
             {
                  subscriber.Enabled = e.Value;
             };
    }
}

It worked fine, until we started using it on smaller machines, when I started getting the occasional:

System.ComponentModel.Win32Exception
Not enough storage is available to process this command

As it turns out, there is one place in the code where subscribers controls are being dynamically created, added and removed from a form. Given my advanced understanding of garbage collection etc (i.e. none, until yesterday), I never thought to clear up behind me, as in the vast majority of cases, the subscribers also live for the lifetime of the application.

I've fiddled around a while with Dustin Campbell's WeakEventHandler, but it doesn't work with anonymous delegates (not for me anyway).

Is there anyway out of this problem? I really would like to avoid having to copy-paste boiler-plate code all over the shop.

(Oh, and don't bother with asking me WHY we are creating and destroying controls all the time, it wasn't my design decision...)

(PS: It's a winforms application, but we've upgraded to VS2008 and .Net 3.5, should I consider using the Weak Event pattern?)

(PPS: Good answer from Rory, but if anyone can come up with an equivalent to the WeakEventHandler which avoids me having to remember to explicitly UnLink/Dispose, that would be cool...)

EDIT I must admit that I worked around this problem by "recycling" the controls in question. However the workaround has come back to haunt me as the 'key' I was using is apparently non-unique (sob). I've just discovered other links here (tried this - seems to be a bit too weak - GC clears delegates even if target is still alive, same problem with s,oɔɯǝɹ answer below), here (forces you to modify publisher, and doesn't really work with anonymous delegates) and here (cited-as-incomplete by Dustin Campbell).

It occurs to me that what I'm looking for may be semantically impossible - closures are designed to 'hang around even after I'm gone'.

I've found another workaround, so I'll stick with that, pending a voice from the gods.

+4  A: 

If you retain a reference to the anonymous delegate and then remove it when the controls are removed from the form that should allow both the controls and the anonymous delegates to be garbage collected.

So something like this:

public static class Linker
{

    //(Non-lambda version, I'm not comfortable with lambdas:)
    public static EventHandler<ValueEventArgs<bool>> Link(Publisher publisher, Control subscriber)
    {
         EventHandler<ValueEventArgs<bool>> handler = delegate(object sender, ValueEventArgs<bool> e)
             {
                  subscriber.Enabled = e.Value;
             };
         publisher.EnabledChanged += handler;
         return handler;
    }

    public static void UnLink(Publisher publisher, EventHandler<ValueEventArgs<bool>> handler)
    {
        publisher.EnabledChanged -= handler;
    }

}

See http://stackoverflow.com/questions/183367/unsubscribe-anonymous-method-in-c for an example of removing delegates.

Rory
Nice, in my case, I could even do this: subscriber.Disposed += (s, e) => component.ValueChanged -= handler; (Oops, a lambda...).
Benjol
+1  A: 

Some sample code i made recently, based on WeakReference:

// strongly typed weak reference
public class WeakReference<T> : WeakReference
    where T : class
{
    public WeakReference(T target)
        : base(target)
    { }

    public WeakReference(T target, bool trackResurrection)
        : base(target, trackResurrection)
    { }

    public new T Target
    {
        get { return base.Target as T; }
        set { base.Target = value; }
    }
}

// weak referenced generic event handler
public class WeakEventHandler<TEventArgs> : WeakReference<EventHandler<TEventArgs>>
    where TEventArgs : EventArgs
{
    public WeakEventHandler(EventHandler<TEventArgs> target)
        : base(target)
    { }

    protected void Invoke(object sender, TEventArgs e)
    {
        if (Target != null)
        {
            Target(sender, e);
        }
    }

    public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> weakEventHandler)
    {
        if (weakEventHandler != null)
        {
            if (weakEventHandler.IsAlive)
            {
                return weakEventHandler.Invoke;
            }
        }

        return null;
    }
}

// weak reference common event handler
public class WeakEventHandler : WeakReference<EventHandler>
{
    public WeakEventHandler(EventHandler target)
        : base(target)
    { }

    protected void Invoke(object sender, EventArgs e)
    {
        if (Target != null)
        {
            Target(sender, e);
        }
    }

    public static implicit operator EventHandler(WeakEventHandler weakEventHandler)
    {
        if (weakEventHandler != null)
        {
            if (weakEventHandler.IsAlive)
            {
                return weakEventHandler.Invoke;
            }
        }

        return null;
    }
}

// observable class, fires events
public class Observable
{
    public Observable() { Console.WriteLine("new Observable()"); }
    ~Observable() { Console.WriteLine("~Observable()"); }

    public event EventHandler OnChange;

    protected virtual void DoOnChange()
    {
        EventHandler handler = OnChange;

        if (handler != null)
        {
            Console.WriteLine("DoOnChange()");
            handler(this, EventArgs.Empty);
        }
    }

    public void Change()
    {
        DoOnChange();
    }
}

// observer, event listener
public class Observer
{
    public Observer() { Console.WriteLine("new Observer()"); }
    ~Observer() { Console.WriteLine("~Observer()"); }

    public void OnChange(object sender, EventArgs e)
    {
        Console.WriteLine("-> Observer.OnChange({0}, {1})", sender, e);
    }
}

// sample usage and test code
public static class Program
{
    static void Main()
    {
        Observable subject = new Observable();
        Observer watcher = new Observer();

        Console.WriteLine("subscribe new WeakEventHandler()\n");
        subject.OnChange += new WeakEventHandler(watcher.OnChange);
        subject.Change();

        Console.WriteLine("\nObserver = null, GC");
        watcher = null;
        GC.Collect(0, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();

        subject.Change();

        if (Debugger.IsAttached)
        {
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}

Generates the following output:

new Observable()
new Observer()
subscribe new WeakEventHandler()

DoOnChange()
-> Observer.OnChange(ConsoleApplication4.Observable, System.EventArgs)

Observer = null, GC
~Observer()
DoOnChange()
~Observable()
Press any key to continue . . .

(Note that unsubscribing (-=) doesn't work)

oɔɯǝɹ
Wow, how did you ever find this question, lost in the mists of time! Haven't got VS to hand right now, but I'll try it next week.
Benjol
tagging rules ...
oɔɯǝɹ
Finally got round to trying this - apparently it's a bit TOO enthusiastic about disposing of my handler. I test with Observer control embedded in Observable form. If I do a GC.Collect, event no longer works, even if control is still there...
Benjol
The event handler will be disposed when no longer referenced. What's too enthousistic about that?
oɔɯǝɹ
+1  A: 

I know that this question is ancient, but hell - I found it, and I figure that others might as well. I'm trying to resolve a related issue, and might have some insight.

You mentioned Dustin Campbell's WeakEventHandler - it indeed cannot work with anonymous methods by design. I was trying to fiddle something together that would, when I realized that a) in 99% of cases I'd need something like this his original solution would be safer, and b) in those few cases where I have to (note: have to, not "want to because lambdas are so much prettier and concise") it's possible to make it work if you get a little clever.

Your example seems like exactly the kind of one-off case where getting a little tricky can result in a fairly concise solution.


public static class Linker {
    public static void Link(Publisher publisher, Control subscriber) {
     // anonymous method references the subscriber only through weak references, so its 
     // existance doesn't interfere with garbage collection
     var subscriber_weak_ref = new WeakReference(subscriber);
     // this instance variable will stay in memory as long as the anonymous method holds a reference to it
     // we declare and initialize it to reserve the memory (also, compiler complains about uninitialized variable otherwise)
     EventHandler<ValueEventArgs<bool>> handler = null;
     // when the handler is created it will grab references to the local variables used within, keeping them in memory after
     // the function scope ends
     handler = delegate(object sender, ValueEventArgs<bool> e) {
      var subscriber_strong_ref = subscriber_weak_ref.Target as Control;
      if (subscriber_strong_ref != null) subscriber_strong_ref.Enabled = e.Value;
      else {
       // unsubscribing the delegate from within itself is risky, but because only one instance exists and nobody else
       // has a reference to it we can do this
       ((Publisher)sender).EnabledChanged -= handler;
       // by assigning the original instance variable pointer to null we make sure that nothing else references
       // the anonymous method and it can be collected. After this, the weak reference and the handler pointer itself
       // will be eligible for collection as well.
       handler = null; 
      }
     };

     publisher.EnabledChanged += handler;
    }
}

The WPF Weak Event pattern is rumored to come with a lot of overhead, so in this particular situation I wouldn't use it. Furthermore, referencing the core WPF library in a WinForm app seems a little heavy as well.

Egor
The question may be ancient, but it still interests me! I've added your solution to my test application and it appears to work, so it is my privilege to award you your first green tick!
Benjol
Thanks! You might also check out my version of Dustin Campbell's implementation. I posted it as an answer to this question:http://stackoverflow.com/questions/1089309/weak-events-in-net/1483010#1483010
Egor
Ouf! That one's a bit complicated for me, I'll have to take time to disect it. I've got another version of this one brewing...
Benjol
A: 

Building further on Egor's answer, I wanted to try and build a version where I didn't have to determine in advance which event I want to attach to.

I've only managed to make it work with generic event handlers: for 'standard' event handlers (eg. FormClosingEventHandler), it's a bit tricky, because you can't have a type constraint where T : delegate (unless your name ends with Pony).

private static void SetAnyGenericHandler<S, T>(
     Action<EventHandler<T>> add,     //to add event listener to publisher
     Action<EventHandler<T>> remove,  //to remove event listener from publisher
     S subscriber,                    //ref to subscriber (to pass to consume)
     Action<S, T> consume)            //called when event is raised*
         where T : EventArgs 
         where S : class
{
    var subscriber_weak_ref = new WeakReference(subscriber);
    EventHandler<T> handler = null;
    handler = delegate(object sender, T e)
    {
        var subscriber_strong_ref = subscriber_weak_ref.Target as S;
        if(subscriber_strong_ref != null)
        {
            Console.WriteLine("New event received by subscriber");
            consume(subscriber_strong_ref, e);
        }
        else
        {
            remove(handler);
            handler = null;
        }
    };
    add(handler);
}

(*I did try EventHandler<T> consume here, but the calling code gets ugly because you have to cast s to Subscriber in the consume lambda.)

Calling code example, taken from example above:

SetAnyGenericHandler(
    h => publisher.EnabledChanged += h, 
    h => publisher.EnabledChanged -= h, 
    subscriber, 
    (Subscriber s, ValueEventArgs<bool> e) => s.Enabled = e.Value);

Or, if you prefer

SetAnyGenericHandler<Subscriber, ValueEventArgs<bool>>(
    h => publisher.EnabledChanged += h, 
    h => publisher.EnabledChanged -= h, 
    subscriber, 
    (s, e) => s.Enabled = e.Value);

It would be nice to be able to pass in the Event as just one parameter, but you can't access add/remove from an event any more than you can access get/set from a property (without doing yucky reflexion stuff, I think).

Benjol