tags:

views:

1633

answers:

3

Problem: I have a document class which contains a list of objects. These objects raise events such as SolutionExpired, DisplayExpired etc. The document needs to respond to this.

Documents can sometimes exchange objects, but a single object should never be 'part' of more than one document.

My document class contains a bunch of methods which serve as event handlers. Whenever an object enters the document, I use AddHandler to set up the events, and whenever an object is removed from the document I use RemoveHandler to undo the damage. However, there are cases where it's difficult to make sure all the steps are properly taken and I might thus end up with rogue event handlers.

Long story short; how do I remove all the handlers that are pointing to a specific method? Note, I don't have a list of potential event sources, these could be stored anywhere.

Something like:

RemoveHandler *.SolutionExpired, AddressOf DefObj_SolutionExpired

+1  A: 
public class TheAnswer
{
    public event EventHandler MyEvent = delegate { };

    public void RemoveFromMyEvent(string methodName)
    {
        foreach (var handler in MyEvent.GetInvocationList())
        {
            if (handler.Method.Name == methodName)
            {
                MyEvent -= (EventHandler)handler;
            }
        }
    }
}

EDIT 2: Apologies for my misunderstanding--I see that you were pretty clear about not having access to the event sources in your original post.

The simplest way I can think of to solve this problem involves implementing a Shared dictionary of object-to-document bindings. When an object enters a document, check the dictionary for an existing binding to another document; if present, remove handlers that refer to the old document before adding them for the new. Either way, update the dictionary with the new binding.

I think in most cases the performance and memory impacts would be negligible: unless you're dealing with many tens of thousands of small objects and frequently exchange them between documents, the memory overhead of each key/value pair and performance hit for each lookup operation should be fairly small.

As an alternative: if you can detect (in the document event handlers) that the sender of the event is no longer relevant to the document, you can detach the events there.

These seem like the kind of ideas you might have already rejected--but maybe not!

Ben M
Ben, thanks for posting this. I'm afraid it's not making a lot of sense to me (or the VB compiler for that matter). Could you please post the original C#?
David Rutten
I knew I should have left the original C# version up. :-)
Ben M
Thanks Ben, I see what you're doing here, but if I'm not mistaken this function assumes I have a reference to the instance which is raising the events. These objects might long since have disappeared over the horizon into another list somewhere.I'm starting to think it's probably not possible to do this since handler delegates are defined in the class which raises events, not the class which defines the handler.
David Rutten
+1  A: 

Why not use Delegate.RemoveAll? (maybe using reflection if the Delegate instance is private)

zproxy
Because I need to have an instance of the source for this.I think... I no longer have access to the event raising object, only the handler.
David Rutten
+2  A: 

You can use Delegate.RemoveAll(). (The part you're interested in is in button2_Click)

public void Form_Load(object sender, EventArgs e) 
{ 
  button1.Click += new EventHandler(button1_Click);
  button1.Click += new EventHandler(button1_Click);
  button2.Click += new EventHandler(button2_Click);
  TestEvent += new EventHandler(Form_TestEvent);
}
event EventHandler TestEvent;
void OnTestEvent(EventArgs e)
{
  if (TestEvent != null)
     TestEvent(this, e);
}
void Form_TestEvent(object sender, EventArgs e)
{
  MessageBox.Show("TestEvent fired");
}
void button2_Click(object sender, EventArgs e)
{
  Delegate d = TestEvent as Delegate;
  TestEvent = Delegate.RemoveAll(d, d) as EventHandler;
}
void button1_Click(object sender, EventArgs e)
{
  OnTestEvent(EventArgs.Empty);
}

You should note that it doesn't alter the contents of the delegates you pass in to it, it returns an altered delegate. Consequently, you won't be able to alter the events on a button you've dropped on a form from the form, as button1.Click can only have += or -= used on it, not =. This won't compile:

button1.Click = Delegate.RemoveAll(d, d) as EventHandler;

Also, be sure that wherever you're implementing this you're watching out for the potential of race conditions. You could end up with some really strange behavior if you're removing handlers from an event that is being called by another thread!

blesh
I don't get how this is acceptable, given the OP's condition of needing to remove handlers from an event to which he doesn't have private-level access. I must be missing something!
Ben M