views:

117

answers:

1

I have a method that, throughout the lifetime of my app, gets added to any number of events. At some point I'd like to remove the method from all of the events that reference it. I only know the method...I would love to NOT keep a local List<EventHandler> of all the events the method gets added to because I think that somewhere/somehow the CLR is doing this for me for the purposes of garbage collection. Some pseudo-code below, any ideas? Thanks...

What I want to do:

public class Subscriber
{
  public virtual void EventRaised(object pSender, EventArgs pArguments)
  {
    MessageBox.Show("EventRaised");
  }

  public virtual void UnsubscribeAll()
  {
    //Something like:
    EventRaised.RemoveReferences();
    //or
    foreach(var MyReference in EventRaised.References)
      MyReference.Remove();
  }
}

var MySubscriber = new Subscriber();

ThisEvent += MySubscriber.EventRaised;
ThatEvent += MySubscriber.EventRaised;
//on and on...

MySubscriber.UnsubscribeAll();

What I don't want to do:

public class Subscriber
{
  protected List<EventHandler> Publishers = new List<EventHandler>();

  public virtual void EventRaised(object pSender, EventArgs pArguments)
  {
    MessageBox.Show("EventRaised");
  }

  public virtual void Subscribe(EventHandler pPublisher)
  {
    pPublisher += EventRaised;
    Publishers.Add(pPublisher);
  }

  public virtual void UnsubscribeAll()
  {
    foreach(var Publisher in Publishers)
      Publisher -= EventRaised;

    Publishers.Clear();
  }
}

var MySubscriber = new Subscriber();

MySubscriber.Subscribe(ThisEvent);
MySubscriber.Subscribe(ThatEvent);
//on and on...

MySubscriber.UnsubscribeAll();

Another Example

If the CLR doesn't keep track of the references per Stephen Cleary - bummer...that forces me into plan B. The answers and comments don't quite fit my problem - it's my fault though so I'll add another example below. JBall's code example #1 is very interesting and I'll add that to my bag of tricks.

I'm not removing the event references to the method with the need to or intention of restoring them later...I just want them gone. How about this pseudo-code - from the viewpoint of the Child, I want to say "forget whatever events my Parent attached to and attach to these events instead." I have several workarounds, including keeping a List<> of attached events, but I was hoping to leverage work that the CLR does for me behind-the-scenes. An added benefit of a CLR-maintained structure would've been the fact that it's definitive and final...there wouldn't have been a chance of attaching a method to an event without actually recording the reference.

public class Parent
{
  public Parent()
  {
    AnObject.ThisEvent += EventRaised;
    AnObject.ThatEvent += EventRaised;
  }

  public virtual void EventRaised(object pSender, EventArgs pArguments)
  {
    MessageBox.Show("EventRaised");
  }
}

public class Child
{
  public Child(): base()
  {
    //What I want to do:
    EventRaised.RemoveAllEventReferences();

    AnotherObject.ThisEvent += EventRaised;
    AnotherObject.ThatEvent += EventRaised;
  }
}
+1  A: 

Have your EventRaised method call another delegate which you by default assign, and when UnsubscribeAll() is called, remove that event from your inner delegate.

public class Subscriber
{
    EventHander m_InnerEvent;

    public Subscriber()
    {
        m_InnerEvent += InnerEventRaised;
    }

    public virtual void EventRaised(object pSender, EventArgs pArguments)
    {
        m_InnerEvent(pSender, pArguments);
    }

    protected virtual void InnerEventRaised(object pSender, EventArgs pArguments)
    {
        Console.WriteLine("EventRaised");
    }

    public virtual void UnsubscribeAll()
    {
        m_InnerEvent -= InnerEventRaised;
    }
}

Alternatively, have a flag that gets set when UnsubscribeAll() is called, and check that flag within your EventRaised method.

public class Subscriber
{
    bool m_Active = true;

    public virtual void EventRaised(object pSender, EventArgs pArguments)
    {
        if(m_Active) { InnerEventRaised(pSender, pArguments); }
    }

    void InnerEventRaised(object pSender, EventArgs pArguments)
    {
        Console.WriteLine("EventRaised");
    }

    public virtual void UnsubscribeAll()
    {
        m_Active = false;
    }
}

For your new example, if this is actually the structure of what you're trying to do, you simply need to refactor. Everything is happening in the constructors and the Child constructor is simply undoing what was done in the Parent constructor. Move the bits of the Parent constructor's actions into a method. Then the Child can override that method to skip what Parent would do and do what it wants instead:

public class Parent
{
    public Parent()
    {
        AttachEventsToThings();
    }

    protected virtual void AttachEventsToThings()
    {
        AnObject.ThisEvent += EventRaised;
        AnObject.ThatEvent += EventRaised;
    }

    public virtual void EventRaised(object pSender, EventArgs pArguments)
    {
        MessageBox.Show("EventRaised");
    }
}

public class Child : Parent
{
    protected override void AttachEventsToThings()
    {
        AnotherObject.ThisEvent += EventRaised;
        AnotherObject.ThatEvent += EventRaised;
    }
}
jball
To clarify, this is the only way to do it. The CLR does not keep a list as you suspect; the GC works via a "mark and sweep" algorithm.
Stephen Cleary
If you need to allow re-subscribing after `UnsubscribeAll`, then you can use the `CallbackContext` class from [Nito.Async](http://nitoasync.codeplex.com/). I have [a blog post](http://nitoprograms.blogspot.com/2009/04/asynchronous-callback-contexts.html) that goes into more detail on the concept.
Stephen Cleary
@Stephen: If this is the correct answer, why didn't you upvote it? Also, your comment received two upvotes, which means this answer should already have three upvotes?
Robert Harvey
@Robert: I believe the answer is correct, but not as complete as I'd like (hence my comments).
Stephen Cleary
Well, I *think* I understand the second option in your answer, but the first option I'm finding a little hard to visualize. Code samples, perhaps?
Robert Harvey
Added examples which are hopefully correct and clear. Editing code in the SO editor is a bit tricky...
jball
It should also be noted that since there is no "list" of subscriptions, they do still exist in the event handler list. The method `UnsubscribeAll` in these examples would be better called `DisableSubscriptions` (and you could add an `EnableSubscriptions` easily).
Stephen Cleary