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

时间:2009-07-15 05:16:42

标签: c# hook event-handling

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

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

// 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”会自动更新。但不,事实并非如此。它的长度总是以一个为单位。

无论如何从包含事件的对象外部确定这个?

5 个答案:

答案 0 :(得分:64)

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

我相信比较调用列表的长度会起作用,但是你需要在里面对象来操作它。

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

为什么你想知道特定事件是否被连接?是避免多次注册吗?

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

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

答案 1 :(得分:49)

C#event关键字提供了一种微妙的错觉,即事件具有调用列表。

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

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

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

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

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

答案 2 :(得分:13)

它可以完成,但它需要一些hackery ...如上所述,编译器生成事件的实现,包括其支持字段。 Reflection允许您按名称检索支持字段,一旦您有权访问它,即使您不在班级本身,也可以调用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;
    }
}

答案 3 :(得分:5)

您应该可以通过“事件”获取调用列表。粗略地说,它会像...

public delegate void MyHandler;
public event MyHandler _MyEvent
public int GetInvocationListLength()
{
   var d = this._MyEvent.GetInvocationList(); //Delegate[]
   return d.Length;
}

答案 4 :(得分:0)

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

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

取决于您的使用情况,使其成为受保护的,内部的或同时受保护的。