How to remove yourself from an event handler?

一曲冷凌霜 提交于 2019-12-30 08:33:28

问题


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?


回答1:


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.




回答2:


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;
    }
}


来源:https://stackoverflow.com/questions/16734755/how-to-remove-yourself-from-an-event-handler

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!