是否已添加事件处理程序?


183

有没有办法判断事件处理程序是否已添加到对象?我正在将对象列表序列化为会话状态或从会话状态中序列化,以便我们可以使用基于SQL的会话状态...当列表中的对象的属性发生更改时,需要对其进行标记,事件处理程序会在此之前对其进行适当的处​​理。但是现在,当对象反序列化时,它没有获取事件处理程序。

为了解决这个问题,我刚刚将事件处理程序添加到访问该对象的Get属性中。现在它被调用了,这很棒,除了它被调用了5次外,所以我认为每次访问该对象时,处理程序都会不断增加。

只需忽略它就足够安全了,但是我宁愿通过检查是否已经添加了处理程序来使它更加整洁,所以我只这样做一次。

那可能吗?

编辑:我不一定完全控制添加了什么事件处理程序,因此仅检查null是不够的。


Answers:


123

如@Telos所述,在定义类的外部,您只能在a +=或a 的左侧使用EventHandler -=。因此,如果您具有修改定义类的能力,则可以提供一种方法来检查事件处理程序是否为null-如果是,则不添加任何事件处理程序。如果不是,那么也许可以遍历Delegate.GetInvocationList中的值 。如果一个等于要添加为事件处理程序的委托,那么您知道它在那里。

public bool IsEventHandlerRegistered(Delegate prospectiveHandler)
{   
    if ( this.EventHandler != null )
    {
        foreach ( Delegate existingHandler in this.EventHandler.GetInvocationList() )
        {
            if ( existingHandler == prospectiveHandler )
            {
                return true;
            }
        }
    }
    return false;
}

并且可以很容易地将其修改为“如果不存在则添加处理程序”。如果您无权访问公开事件的班级内部,则可能需要探索-=+=,如@Lou Franco所建议。

但是,最好重新检查一下调试和停用这些对象的方式,以查看是否找不到自己跟踪这些信息的方法。


7
这不会编译,EventHandler只能位于+ =或-=的左侧。
CodeRedick

2
根据进一步的解释,撤消了反对票。SQL状态几乎破坏了整个构想……:(
CodeRedick

1
感谢Blair和SO搜索,正是我一直在寻找的东西(烦人的,但是您无法在课堂上做这件事)
George Mauer

3
在比较代表是否平等时,大多数情况下会出现问题。因此,Delegate.Equals(objA, objB)如果要检查完全相同的委托存在,请使用。否则,分别比较属性if(objA.Method.Name == objB.Method.Name && objA.Target.GetType().FullName == objB.Target.GetType().FullName)
2014年

2
此代码在WinForm中不起作用。严格来说是ASP.NET吗?
jp2code 2015年

211

最近,我遇到了类似的情况,我只需要为事件注册一次处理程序。我发现您可以安全地先注销,然后再重新注册,即使处理程序根本没有注册:

myClass.MyEvent -= MyHandler;
myClass.MyEvent += MyHandler;

请注意,每次注册处理程序时都要这样做,以确保您的处理程序仅注册一次。听起来对我来说是一个很好的实践:)


9
似乎有风险;如果在删除处理程序之后又将其添加回之前触发了一个事件,则将丢失该事件。
Jimmy 2012年

27
当然。您的意思是线程不安全。但这仅在运行多个线程等时才是问题,这是不常见的。在大多数情况下,为了简单起见,这应该足够好。
2012年

6
这困扰着我。仅仅因为您当前还没有在代码中显式创建线程,这并不意味着就不会有多个线程,也不会在以后添加它们。一旦您(或可能在几个月后,团队中的其他人)添加了工作线程或对UI和网络连接都做出了响应,便为高度间歇性的丢弃事件打开了大门。
Technophile

1
我认为删除和再次添加之间的时间窗口非常短,几乎不可能存在错过的事件,但是是的,它仍然是可能的。
Alisson

2
如果您将其用于更新UI等操作,那么这样的琐碎任务就可以了。如果这是用于网络数据包处理等,那么我将不使用它。
轧辊

18

如果这是唯一的处理程序,则可以检查该事件是否为null,如果不是,则添加了该处理程序。

我认为即使没有添加事件,您也可以与处理程序一起安全地在事件上调用-=(如果没有,则可以捕获它),以确保在添加事件之前不存在该事件。


3
一旦在其他地方处理事件,此逻辑也会中断。
bugged87

6

本示例说明如何使用方法GetInvocationList()检索所有已添加的处理程序的委托。如果要查看是否已添加特定的处理程序(函数),则可以使用数组。

public class MyClass
{
  event Action MyEvent;
}

...

MyClass myClass = new MyClass();
myClass.MyEvent += SomeFunction;

...

Action[] handlers = myClass.MyEvent.GetInvocationList(); //this will be an array of 1 in this example

Console.WriteLine(handlers[0].Method.Name);//prints the name of the method

您可以检查委托的Method属性上的各种属性,以查看是否已添加特定功能。

如果要查看是否只有一个附件,则可以测试是否为空。


GetInvocationList()不是我的课程的成员。实际上,我似乎无法在我可以访问的任何对象或处理程序上找到此方法...
CodeRedick

我也尝试过,显然您只能从类内部访问该事件。我通常是这样做的,正如其他人提到的那样,事件处理程序可能无论如何都会迷路。感谢您的澄清!
CodeRedick

4

如果我正确理解了您的问题,则可能会遇到更大的问题。您说其他对象可以订阅这些事件。当对象被序列化和反序列化时,其他对象(您无法控制的对象)将丢失其事件处理程序。

如果您不担心,那么保留对​​事件处理程序的引用就足够了。如果您担心其他对象丢失事件处理程序的副作用,那么您可能需要重新考虑您的缓存策略。


1
天哪!甚至都没有想到……尽管考虑到我最初的问题是我自己的处理程序迷路了,这应该很明显。
CodeRedick

2

对我有用的唯一方法是创建一个布尔变量,在添加事件时将其设置为true。然后我问:如果变量为false,则添加事件。

bool alreadyAdded = false;

此变量可以是全局变量。

if(!alreadyAdded)
{
    myClass.MyEvent += MyHandler;
    alreadyAdded = true;
}

1

我同意alf的回答,但对其使用的修改很少,

           try
            {
                control_name.Click -= event_Click;
                main_browser.Document.Click += Document_Click;
            }
            catch(Exception exce)
            {
                main_browser.Document.Click += Document_Click;
            }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.