【问题标题】:How to remove yourself from an event handler?如何从事件处理程序中删除自己?
【发布时间】:2013-05-24 12:16:01
【问题描述】:

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

我有一个FileSystemWatcher。如果创建/重命名文件,它会检查其名称。如果匹配,则将其移动到特定位置。但是,如果文件被锁定,它会生成一个附加到计时器的滴答事件的 lambda,一直等到文件未被锁定。如果不是,它会移动文件,然后将自己从事件处理程序中删除。我见过很多方法可以做到这一点,比如保留实例,或者创建一个命名方法。我不能在这里做任何一个。我有哪些选择?

【问题讨论】:

  • 有几种模式允许您以 松散耦合 的方式订阅事件。查看this。它描述了如何订阅/取消订阅事件聚合器(在这种情况下,它是关于屏幕的)。实际的聚合器创建对象之间的引用。那里有很多不同的实现。我更喜欢的是在Caliburn.Micro中实现的。

标签: c# events timer .net-3.5 lambda


【解决方案1】:

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

首选方法:

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

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. 可能根本没有字段。如果事件使用显式事件实现并且只是委托给另一个事件或以其他方式存储委托,则会发生这种情况。

结论:

替代方法依赖于实现细节,因此如果可以避免,请不要使用它。

【讨论】:

  • 我忘记了闭包!哎呀!谢谢你的回答,你比我走得更远。
  • 我认为您对 Dark Falcon 的回答早于您的回答写了负面评论,然后在您的首选方法中给出了基本相同的建议,这有点不公平。
  • @BenVoigt 好吧,他的评论是对的,从技术上讲,这不是一个答案,即使它可能是首选解决方案。另请注意,他并没有否决 Dark 的回答,只是评论说它在技术上不符合规定的标准。另一个关键区别是 Daniel 包含了一个确实符合问题标准的解决方案。
  • @Servy:谢谢,正是我的想法。此外,我从未打算让 Dark Falcon 删除他的答案。
【解决方案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;
    }
}

【讨论】:

    猜你喜欢
    • 2021-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    • 2010-10-22
    相关资源
    最近更新 更多