How to remove yourself from an event handler?
Asked Answered
Q

2

11

What I want to do is basically remove a function from an event, without knowing the function's name.

I have a FileSystemWatcher. If a file is created/renamed it checks its name. If it matches, it then moves it to a specific location. However, if the file is locked, it makes a lambda that attaches to a timer's tick event, waiting until the file is not locked. When it isn't, it moves the file and then removes itself from the event handler. I've seen lots of ways to do this, like keeping the instance, or making a named method. I can't do either of those here. What are my options?

Quassia answered 24/5, 2013 at 12:16 Comment(1)
There is a couple of patterns out there which allow you to subscribe to events in a loosley coupled way. Check out this. It describes how to subscribe/unsubscribe to an event aggregator (in this case it's about screens). The actual aggregator creates the references between objects. There are a lot of different implementations out there. The one I prefer is implemented in Caliburn.Micro.Turbit
W
31

There is no simple method to achieve this.

Preferred approach:

I don't see why you can't save the delegate. You don't have to save the instance as some field. It can be a local variable that is captured by your anonymous event handler:

EventHandler<TypeOfEventArgs> handler = null;
handler = (s, e) =>
{
    // Do whatever you need to do here

    // Remove event:
    foo.Event -= handler;
}

foo.Event += handler;

I can't think of a single scenario where you can't use this.

Alternative approach without saving the delegate:

However, if you have such a scenario, it get's quite tricky.
You need to find the delegate that has been added as a handler to the event. Because you didn't save it, it is pretty hard to obtain it. There is no this to get a delegate of the currently executing method.

You can't use GetInvocationList() on the event either, because accessing an event outside the class it is defined in is restricted to adding and removing handlers, i.e. += and -=.

Creating a new delegate isn't possible either. While you can get access to the MethodInfo object defining your anonymous method, you can't get access to the instance of the class that method is declared in. This class is generated automatically by the compiler and calling this inside the anonymous method will return the instance of the class your normal method is defined in.

The only way I found that works is to find the field - if any - that the event uses and call GetInvocationList() on it. The following code demonstrates this with a dummy class:

void Main()
{
    var foo = new Foo();
    foo.Bar += (s, e) => {
        Console.WriteLine("Executed");
        
        var self = new StackFrame().GetMethod();
        var eventField = foo.GetType()
                            .GetField("Bar", BindingFlags.NonPublic | 
                                             BindingFlags.Instance);
        if(eventField == null)
            return;
        var eventValue = eventField.GetValue(foo) as EventHandler;
        if(eventValue == null)
            return;
        var eventHandler = eventValue.GetInvocationList()
                                     .OfType<EventHandler>()
                                     .FirstOrDefault(x => x.Method == self)
                               as EventHandler;
        if(eventHandler != null)
            foo.Bar -= eventHandler;
    };
    
    foo.RaiseBar();
    foo.RaiseBar();
}

public class Foo
{
    public event EventHandler Bar;
    public void RaiseBar()
    { 
        var handler = Bar;
        if(handler != null)
            handler(this, EventArgs.Empty);
    }
}

Please note that the string "Bar" that is passed to GetField needs to be the exact name of the field that is used by the event. This results in two problems:

  1. The field can be named differently, e.g. when using an explicit event implementation. You need to manually find out the field name.
  2. There might be no field at all. This happens if the event uses an explicit event implementation and just delegates to another event or stores the delegates in some other way.

Conclusion:

The alternative approach relies on implementation details, so don't use it if you can avoid it.

Wally answered 24/5, 2013 at 12:59 Comment(4)
I forgot about closures! Whoops! Thanks for the answer though, you went even further than I can.Disunity
I think it's a little unfair that you wrote a negative comment on Dark Falcon's answer which predated yours, then gave essentially the same advice in your preferred approach.Ralphralston
@BenVoigt Well, his comment was right, it's technically not an answer, even though if it is possible it's the preferred solution. Also note he didn't downvote Dark's answer, merely commented saying that it didn't technically meet the stated criteria. Another key difference is that Daniel included a solution that does meet the criteria in the question.Ottilie
@Servy: Thanks, just what I thought. In addition, I never intended for Dark Falcon to delete his answer.Wally
P
0

Steps to remove event handler with lambda expression:

public partial class Form1 : Form
{
    private dynamic myEventHandler;
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        myEventHandler = new System.EventHandler((sender2, e2) => this.button1_Click(sender, e, "Hi there"));
        this.button1.Click += myEventHandler;
    }

    private void button1_Click(object sender, EventArgs e, string additionalInfo)
    {
        MessageBox.Show(additionalInfo);
        button1.Click -= myEventHandler;
    }
}
Piggyback answered 19/11, 2014 at 10:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.