web-dev-qa-db-ja.com

C#イベントが接続されているかどうかを確認する方法

イベントが接続されているかどうかを調べたいです。私は見回しましたが、イベントを含むオブジェクトの内部を変更することを含む解決策を見つけました。私はこれをやりたくありません。

これがうまくいくと思ったいくつかのテストコードです。

// 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」は、イベントをフックすると自動的に更新されると思いました。しかし、そうではありません。この長さは常に1に戻ります。

とにかく、イベントを含むオブジェクトの外部からこれを決定する方法はありますか?

37
Nick

C#eventキーワードによって提示される微妙な錯覚があります。つまり、イベントには呼び出しリストがあります。

C#eventキーワードを使用してイベントを宣言すると、コンパイラーはクラスでプライベートデリゲートを生成し、それを管理します。イベントをサブスクライブするたびに、コンパイラが生成したaddメソッドが呼び出され、デリゲートの呼び出しリストにイベントハンドラーが追加されます。イベントの明示的な呼び出しリストはありません。

したがって、デリゲートの呼び出しリストを取得する唯一の方法は、できれば次のことです。

  • リフレクションを使用してコンパイラーが生成したデリゲートにアクセスするOR
  • 非プライベートデリゲート(おそらく内部)を作成し、イベントのadd/removeメソッドを手動で実装します(これにより、コンパイラはイベントのデフォルト実装を生成できなくなります)

後者の手法を示す例を次に示します。

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

関係するオブジェクトがイベントキーワードを指定している場合、できることはadd(+=)と削除(-=)ハンドラー。

呼び出しリストの長さを比較するとうまくいくと思いますが、それを取得するにはinsideオブジェクトを操作する必要があります。

また、+=および-=演算子は新しいイベントオブジェクトを返します。既存のものを変更しません。

特定のイベントが接続されているかどうかを知りたいのはなぜですか?複数回登録するのを避けるためですか?

その場合、最初にハンドラーを削除するのがコツです(-=)存在しないハンドラーを削除することは正当であり、何も行いません。例えば:

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

それはできますが、いくつかのハッカーが必要です...上記のように、コンパイラはバッキングフィールドを含むイベントの実装を生成します。 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;
    }
}
13
STW

「イベント」を介して呼び出しリストを取得できるはずです。大体、それは次のようなものになります。

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