C#如何查找事件是不是已连接

Posted

技术标签:

【中文标题】C#如何查找事件是不是已连接【英文标题】:C# How to find if an event is hooked upC#如何查找事件是否已连接 【发布时间】:2010-11-10 21:35:45 【问题描述】:

我希望能够查明某个事件是否已连接。我环顾四周,但只找到了涉及修改包含事件的对象内部的解决方案。我不想这样做。

这是一些我认为可行的测试代码:

// Create a new event handler that takes in the function I want to execute when the event fires
EventHandler myEventHandler = new EventHandler(myObject_SomeEvent);
// Get "p1" number events that got hooked up to myEventHandler
int p1 = myEventHandler.GetInvocationList().Length;
// Now actually hook an event up
myObject.SomeEvent += m_myEventHandler;
// Re check "p2" number of events hooked up to myEventHandler
int p2 = myEventHandler.GetInvocationList().Length;

请注意以上内容是完全错误的。我认为当我将事件挂钩时,myEventHandler 中的“invocationList”会以某种方式自动更新。但不,事实并非如此。 this 的长度总是返回为一。

是否可以从包含事件的对象外部确定这一点?

【问题讨论】:

【参考方案1】:

如果相关对象已指定 event 关键字,那么您唯一能做的就是添加 (+=) 和删除 (-=) 处理程序,仅此而已。

我相信比较调用列表的长度会起作用,但您需要在对象内部操作才能得到它。

另外,请记住+=-= 运算符返回一个新的事件对象;他们不会修改现有的。

您为什么想知道某个特定事件是否已关联?是为了避免多次注册吗?

如果是这样,诀窍是首先删除处理程序 (-=),因为删除不存在的处理程序是合法的,并且什么都不做。例如:

// Ensure we don't end up being triggered multiple times by the event
myObject.KeyEvent -= KeyEventHandler;
myObject.KeyEvent += KeyEventHandler;

【讨论】:

如果你多次 += KeyEventHandler ,将 -= KeyEventHandler 全部删除还是只删除最后一个,只删除第一个? -= 将删除一个;鉴于他们都是平等的,我不知道有什么方法可以确定哪一个。【参考方案2】:

C# event 关键字呈现出一种微妙的错觉,那就是事件有一个调用列表。

如果您使用 C# event 关键字声明事件,编译器将在您的类中生成一个私有委托,并为您管理它。每当您订阅事件时,都会调用编译器生成的add 方法,这会将事件处理程序附加到委托的调用列表中。该事件没有明确的调用列表。

因此,获得委托的调用列表的唯一方法是最好:

使用反射访问编译器生成的委托或 创建非私有委托(可能是内部委托)并手动实现事件的添加/删除方法(这会阻止编译器生成事件的默认实现)

这是一个演示后一种技术的示例。

class MyType

    internal EventHandler<int> _delegate;
    public event EventHandler<int> MyEvent;
    
        add  _delegate += value; 
        remove  _delegate -= value; 
    

【讨论】:

在尝试之前我不确定的一个澄清是,编译器生成的委托与您在代码中声明的事件具有相同的名称(或至少我的)。跨度> 【参考方案3】:

可以做到,但是需要一些技巧……如上所述,编译器会生成事件的实现,包括其支持字段。反射使您可以按名称检索支持字段,一旦您可以访问它,您就可以调用GetInvocationList(),即使您在类本身之外。

由于您要求使用反射来按名称获取事件,我假设您也在使用反射来按名称获取类型——我正在准备一个示例来说明如何做到这一点。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Reflection;

namespace ConsoleApplication1

    class Program
    
        static void Main(string[] args)
        
            string typeName = "ConsoleApplication1.SomeClass, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
            string eventName = "SomeEvent";

            Type declaringType = Type.GetType(typeName);
            object target = Activator.CreateInstance(declaringType);

            EventHandler eventDelegate;
            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null)  Console.WriteLine("No listeners"); 

            // attach a listener
            SomeClass bleh = (SomeClass)target;
            bleh.SomeEvent += delegate  ;
            //

            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null)
             
                Console.WriteLine("No listeners"); 
            
            else
             
                Console.WriteLine("Listeners: " + eventDelegate.GetInvocationList().Length); 
            

            Console.ReadKey();

        

        static EventHandler GetEventHandler(object classInstance, string eventName)
        
            Type classType = classInstance.GetType();
            FieldInfo eventField = classType.GetField(eventName, BindingFlags.GetField
                                                               | BindingFlags.NonPublic
                                                               | BindingFlags.Instance);

            EventHandler eventDelegate = (EventHandler)eventField.GetValue(classInstance);

            // eventDelegate will be null if no listeners are attached to the event
            if (eventDelegate == null)
            
                return null;
            

            return eventDelegate;
        
    

    class SomeClass
    
        public event EventHandler SomeEvent;
    

【讨论】:

我发现这个答案很有帮助,即使 GetEventHandler() 方法返回一个空事件字段。 (我怀疑这与我将基于 Castle 的动态代理传递给例程而不是代理对象有关。)我正在/正在演示如何使用动态代理来“自动”实现 INotifyPropertyChanged。 你可以直接从 GetEventHandler 方法返回 eventDelegate 而不做最后的空检查。 我收到一条错误消息“无法将 'CallStatechanged' 类型的对象转换为 'System.EventHandler' 类型”CallStateChanged 是我的事件的名称 GetField 返回空值。 GetEvent 返回所需事件但不允许调用 GetValue,因为 EventInfo 不包含此方法。 投射到EventHandler不够通用,我建议使用return (Delegate)eventField.GetValue(classInstance)【参考方案4】:

您应该能够通过“事件”获取调用列表。大概是这样的……

public delegate void MyHandler;
public event MyHandler _MyEvent
public int GetInvocationListLength()

   var d = this._MyEvent.GetInvocationList(); //Delegate[]
   return d.Length;

【讨论】:

这只会在声明事件的类内部起作用;他正试图在外面做。【参考方案5】:

我使用了您的示例并对其进行了一些修改。注册事件处理程序会增加调用次数。即使使用两种不同的回调方法(如此处所示)或使用相同的回调方法。

private void SomeMethod()

    // Create a new event handler that takes in the function I want to execute when the event fires
    var myEventHandler = new EventHandler(OnPropertyChanged);

    // Get "p1" number events that got hooked up to myEventHandler
    int p1 = myEventHandler.GetInvocationList().Length; // 1

    // Now actually hook an event up
    myEventHandler += OnPropertyChanged2;

    // Re check "p2" number of events hooked up to myEventHandler
    int p2 = myEventHandler.GetInvocationList().Length; // 2

    myEventHandler.Invoke(null, null); 
// each of the registered callback methods are executed once. 
// or if the same callback is used, then twice.


private void OnPropertyChanged2(object? sender, EventArgs e)

private void OnPropertyChanged(object? sender, EventArgs e)

正如其他人已经提到的,对 eventhandler.GetInvocationList 的访问仅限于类本身,您需要公开一个属性或方法来检索委托列表。

像这样:

protected Delegate[]? GetInvocations() => PropertyChanged?.GetInvocationList();

根据您的使用情况,使其受到保护,内部或两者兼而有之。

【讨论】:

以上是关于C#如何查找事件是不是已连接的主要内容,如果未能解决你的问题,请参考以下文章

我如何检查在 C# 中的其他按钮单击事件中是不是发生了离开事件

如何捕获Java连接线程中每个子进程的“已终止”事件

C#如何检查事件是不是异步执行

如何判断事件是不是来自 C# 中的用户输入?

C#里事件和委托有啥区别啊??

c# 在groupbox控件中判断的内嵌的radiobutton是不是已选