如何注销“匿名”事件处理程序

38

如果我监听一个事件:

Subject.NewEvent += delegate(object sender, NewEventArgs e)
{
    //some code
}); 

那么我该如何取消注册此事件?或者让内存泄漏?


请参考以下链接:https://dev59.com/cnVC5IYBdhLWcg3wykaj - Mikhail Poda
7个回答

46

为匿名委托实例指定一个名称:

EventHandler<NewEventArg> handler = delegate(object sender, NewEventArgs e)
{
    //some code
};

Subject.NewEvent += handler;
Subject.NewEvent -= handler;

4
为什么这比将其作为非匿名方法更好?这显然不太明显。 - Reed Copsey
2
@PK:我认为这是你可以接近的最近的状态。您无法注销无法引用的内容。 - dtb
12
匿名委托的另一个好处是创建闭包,非匿名方法无法做到这一点。如果发帖人想要在范围内包含一个无法传递给事件参数的值,则这是最佳方法。 - apiguy
1
代码示例很可能不起作用,事件很可能永远不会被触发。取消订阅必须在处理程序内部进行。我刚刚在这里发布了一个类似的问题:http://stackoverflow.com/questions/2147116/event-handling-with-an-anonymous-delegate - herzmeister
1
这可能意味着如果您在类的不同部分进行取消订阅,则需要将委托创建为私有成员,因此与使用方法相比并没有太大的收益。 - Chris S
显示剩余3条评论

25
如果您需要注销事件,我建议避免使用匿名委托作为事件处理程序。 将其分配给本地方法是更好的选择 - 您可以从事件中干净地取消订阅。

9
我不同意-如果你需要创建一个闭包,那么你必须使用一个匿名方法。 - apiguy
3
@free-dom: 总是有避免闭包的选项(最坏情况下,您可以像编译器为您所做的那样操作)。在我看来,大多数计划取消订阅事件的事件处理程序并不适合使用需要闭包的事件。您应该使用易于跟踪的类级状态信息,而不是让编译器为您创建闭包。在这种情况下,闭包往往会导致难以跟踪的奇怪问题,并且不太易于维护。 - Reed Copsey
10
我非常不同意这个观点。闭包可以让你更加清晰而简洁地传递状态。创建小类并在它们上设置状态很糟糕而且很难维护。 - user24359
说真的吗?在很多情况下,具有本地方法只会导致糟糕的面向对象编程。使用匿名委托是一个更好的方法。只需要保留一个引用以便分离事件处理程序,并将其包装在聚合IDisposable中,就可以轻松清理所有事件处理程序。 - Enigmativity
拥有本地方法只会导致糟糕的面向对象编程,嗯...什么? - Justin Skiles
显示剩余2条评论

23

在第一次调用时移除处理程序:

//SubjectType Subject = ..... already defined if using (2)

EventHandler handler = null;
handler = delegate(object sender, EventArgs e)
{
    // (1)
    (sender as SubjectType).NewEvent -= handler;
    // or
    // (2) Subject.NewEvent -= handler;

    // do stuff here
};

Subject.NewEvent += handler;

使用这种代码,Resharper会抱怨访问修改的闭包...这种方法可靠吗?我的意思是,我们确定匿名方法体内的'foo'变量确实引用了匿名方法本身吗? - BladeWise
我想我自己找到了答案:Resharper是正确的,捕获的变量(如上面的示例中的'handler')一旦被分配给匿名方法就会改变。所以,它确实会改变,但这种机制确保'handler'变量存储对匿名方法本身的引用。 - BladeWise
我不得不这样做,以便处理程序可以捕获状态(例如调用方法的参数和本地变量)以及来自事件引发者的状态,所有这些都是因为在调用方法退出之前,引发者没有以任何其他方式提供所需的信息。这很痛苦,但它起作用了,并且不需要创建一个人工类来处理事件。 - Kit

6

您可以创建一个方法,用于取消注册所有事件的侦听器。这不完全是您想要的,但有时它可能会有所帮助。例如(这确实有效 =)):

    class Program {
    static void Main(string[] args) {
        A someClass = new A();
        someClass.SomeEvent += delegate(object sender, EventArgs e) {
            throw new NotImplementedException();
        };

        someClass.ClearEventHandlers();
        someClass.FireEvent();

        Console.WriteLine("No error.");
    }

    public class A {
        public event EventHandler SomeEvent;

        public void ClearEventHandlers() {
            Delegate[] delegates = SomeEvent.GetInvocationList();
            foreach (Delegate delegate in delegates) {
                SomeEvent -= (EventHandler) delegate;
            }
        }

        public void FireEvent() {
            if (SomeEvent != null) {
                SomeEvent(null, null);
            }
        }
    }
}

2
不确定为什么这个被投票否决了--当然,最好一开始就不要有持久的匿名方法,但如果你处于需要清理它们的情况下,这个方法非常有效。 - Guy Starbuck
最好直接设置 someClass.SomeEvent = null,而不是遍历 InvocationList。 - einord

2
您需要为您的匿名函数取一个名称,然后只有在该名称在范围内时才能执行它:
    var handler = new EventHandler(delegate(object o, EventArgs e)
    {
        //do something...
    };

    Subject.NewEvent += handler;

    // later on while handler is still in scope...

    Subject.NewEvent -= handler;

0

0
你需要注销它,除了泄漏之外的其他原因吗?
关于“或者只是允许内存泄漏”的部分,当垃圾回收器清理Subject时,你的匿名委托也应该被清理,所以不应该有泄漏。

内存泄漏是一个原因,另一个原因可能是我想停止监听事件。 - P.K
1
那么你需要将它存储起来,就像dtb的回答所建议的那样。 - Walt W
6
很不幸,这可能会导致泄漏。只要“Subject”仍然具有根引用,就永远不会收集“this”,因为“Subject.NewEvent”后面的委托将持有“this”的强引用,直到“Subject”被取消根引用。正是出于这个原因,才存在WeakEvent模式。 - Reed Copsey
@Reed:啊,所以如果在匿名委托中使用“this”,那么它会创建一个循环引用(对象<->委托),从而防止垃圾收集器清理?你是这个意思吗? - Walt W
3
不,垃圾回收机制可以很好地处理循环引用。但是,如果Subject从未被垃圾回收(例如它可能是应用程序生存期内的“MainFrame”),那么匿名委托和委托中隐含的“this”也不会被回收。 - nos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接