如何从事件处理程序中删除自己

本文关键字:删除 自己 程序 事件处理 | 更新日期: 2023-09-27 18:15:05

我想做的就是从事件中删除一个函数,而不知道函数的名称。

有一个FileSystemWatcher。如果创建/重命名一个文件,它会检查它的名称。如果匹配,它就把它移动到一个特定的位置。但是,如果文件被锁定,它将生成一个附加到计时器tick事件的lambda,直到文件未被锁定为止。如果不是,则移动文件,然后从事件处理程序中删除自身。我见过很多这样做的方法,比如保留实例,或者创建一个命名方法。这两个我都做不到。我有什么选择?

如何从事件处理程序中删除自己

没有简单的方法可以做到这一点。

<标题>首选的方法:

我不明白为什么你不能保存委托。您不必将实例保存为某个字段。它可以是由匿名事件处理程序捕获的本地变量:

EventHandler<TypeOfEventArgs> handler = null;
handler = (s, e) =>
{
    // Do whatever you need to do here
    // Remove event:
    foo.Event -= handler;
}
foo.Event += handler;

我想不出一个你不能使用这个的场景。

不保存委托的替代方法:

然而,如果你有这样的场景,它会变得相当棘手。
您需要找到已添加为事件处理程序的委托。因为你没有保存它,所以很难得到它。没有this来获取当前执行方法的委托。

你也不能在事件上使用GetInvocationList(),因为在类之外访问事件被限制为添加和删除处理程序,即+=-=

创建一个新的委托也不可能。虽然您可以访问定义匿名方法的MethodInfo对象,但无法访问声明该方法的类的实例。这个类是由编译器自动生成的,在匿名方法中调用this将返回定义普通方法的类的实例。

我发现唯一有效的方法是找到事件使用的字段-如果有的话-并对其调用GetInvocationList()。下面的代码用一个虚拟类来演示:

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

请注意,传递给GetField的字符串"Bar"需要是事件使用的字段的确切名称。这会导致两个问题:

  1. 该字段可以以不同的方式命名,例如当使用显式事件实现时。您需要手动查找字段名。
  2. 可能根本没有字段。如果事件使用显式的事件实现,并且只是委托给另一个事件或以其他方式存储委托,则会发生这种情况。
<标题>结论:

另一种方法依赖于实现细节,所以如果可以避免,不要使用它。

删除带有lambda表达式的事件处理程序的步骤:

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