32

C# イベントを動的にサブスクライブして、オブジェクト インスタンスとイベントの名前を含む文字列名を指定すると、そのイベントをサブスクライブして、そのイベントが発生したときに何か (たとえば、コンソールに書き込む) を行うにはどうすればよいでしょうか?

Reflection を使用すると、これは不可能なように思われます。可能であれば、Reflection.Emit を使用する必要がないようにしたいと思います。これが現在 (私にとって) それを行う唯一の方法のように思われるからです。

/EDIT:イベントに必要なデリゲートの署名がわかりません。これが問題の核心です

/編集 2:デリゲートの反変性は良い計画のように思えますが、このソリューションを使用するために必要な仮定を立てることはできません

4

9 に答える 9

29

任意の型のイベントのイベント ハンドラーとして、引数なしで void メソッドを使用するように式ツリーをコンパイルできます。他のイベント ハンドラー タイプに対応するには、イベント ハンドラーのパラメーターを何らかの方法でイベントにマップする必要があります。

 using System;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Reflection;

 class ExampleEventArgs : EventArgs
 {
    public int IntArg {get; set;}
 }

 class EventRaiser
 { 
     public event EventHandler SomethingHappened;
     public event EventHandler<ExampleEventArgs> SomethingHappenedWithArg;

     public void RaiseEvents()
     {
         if (SomethingHappened!=null) SomethingHappened(this, EventArgs.Empty);

         if (SomethingHappenedWithArg!=null) 
         {
            SomethingHappenedWithArg(this, new ExampleEventArgs{IntArg = 5});
         }
     }
 }

 class Handler
 { 
     public void HandleEvent() { Console.WriteLine("Handler.HandleEvent() called.");}
     public void HandleEventWithArg(int arg) { Console.WriteLine("Arg: {0}",arg);    }
 }

 static class EventProxy
 { 
     //void delegates with no parameters
     static public Delegate Create(EventInfo evt, Action d)
     { 
         var handlerType = evt.EventHandlerType;
         var eventParams = handlerType.GetMethod("Invoke").GetParameters();

         //lambda: (object x0, EventArgs x1) => d()
         var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x"));
         var body = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"));
         var lambda = Expression.Lambda(body,parameters.ToArray());
         return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
     }

     //void delegate with one parameter
     static public Delegate Create<T>(EventInfo evt, Action<T> d)
     {
         var handlerType = evt.EventHandlerType;
         var eventParams = handlerType.GetMethod("Invoke").GetParameters();

         //lambda: (object x0, ExampleEventArgs x1) => d(x1.IntArg)
         var parameters = eventParams.Select(p=>Expression.Parameter(p.ParameterType,"x")).ToArray();
         var arg    = getArgExpression(parameters[1], typeof(T));
         var body   = Expression.Call(Expression.Constant(d),d.GetType().GetMethod("Invoke"), arg);
         var lambda = Expression.Lambda(body,parameters);
         return Delegate.CreateDelegate(handlerType, lambda.Compile(), "Invoke", false);
     }

     //returns an expression that represents an argument to be passed to the delegate
     static Expression getArgExpression(ParameterExpression eventArgs, Type handlerArgType)
     {
        if (eventArgs.Type==typeof(ExampleEventArgs) && handlerArgType==typeof(int))
        {
           //"x1.IntArg"
           var memberInfo = eventArgs.Type.GetMember("IntArg")[0];
           return Expression.MakeMemberAccess(eventArgs,memberInfo);
        }

        throw new NotSupportedException(eventArgs+"->"+handlerArgType);
     }
 }


 static class Test
 {
     public static void Main()
     { 
        var raiser  = new EventRaiser();
        var handler = new Handler();

        //void delegate with no parameters
        string eventName = "SomethingHappened";
        var eventinfo = raiser.GetType().GetEvent(eventName);
        eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,handler.HandleEvent));

        //void delegate with one parameter
        string eventName2 = "SomethingHappenedWithArg";
        var eventInfo2 = raiser.GetType().GetEvent(eventName2);
        eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,handler.HandleEventWithArg));

        //or even just:
        eventinfo.AddEventHandler(raiser,EventProxy.Create(eventinfo,()=>Console.WriteLine("!")));  
        eventInfo2.AddEventHandler(raiser,EventProxy.Create<int>(eventInfo2,i=>Console.WriteLine(i+"!")));

        raiser.RaiseEvents();
     }
 }
于 2008-09-05T14:14:37.080 に答える
9

これは完全に一般的な解決策ではありませんが、すべてのイベントが void Foo(object o, T args) の形式であり、T が EventArgs から派生している場合は、デリゲートの反変性を使用して回避できます。このように (KeyDown の署名は Click の署名と同じではありません):

    public Form1()
    {
        Button b = new Button();
        TextBox tb = new TextBox();

        this.Controls.Add(b);
        this.Controls.Add(tb);
        WireUp(b, "Click", "Clickbutton");
        WireUp(tb, "KeyDown", "Clickbutton");
    }

    void WireUp(object o, string eventname, string methodname)
    {
        EventInfo ei = o.GetType().GetEvent(eventname);

        MethodInfo mi = this.GetType().GetMethod(methodname, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);

        Delegate del = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);

        ei.AddEventHandler(o, del);

    }
    void Clickbutton(object sender, System.EventArgs e)
    {
        MessageBox.Show("hello!");
    }
于 2008-09-05T14:25:09.113 に答える
3

Reflection を使用してイベントにサブスクライブすることが可能です

var o = new SomeObjectWithEvent;
o.GetType().GetEvent("SomeEvent").AddEventHandler(...);

http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx

ここにあなたが解決しなければならない問題があります。各イベント ハンドラーに必要なデリゲートには、異なる署名があります。これらのメソッドを動的に作成する方法を見つける必要があります。これはおそらく Reflection.Emit を意味します。または、コンパイルされたコードで処理できるように、自分自身を特定のデリゲートに制限する必要があります。

お役に立てれば。

于 2008-09-05T13:23:01.370 に答える
2
public TestForm()
{
    Button b = new Button();

    this.Controls.Add(b);

    MethodInfo method = typeof(TestForm).GetMethod("Clickbutton",
    BindingFlags.NonPublic | BindingFlags.Instance);
    Type type = typeof(EventHandler);

    Delegate handler = Delegate.CreateDelegate(type, this, method);

    EventInfo eventInfo = cbo.GetType().GetEvent("Click");

    eventInfo.AddEventHandler(b, handler);

}

void Clickbutton(object sender, System.EventArgs e)
{
    // Code here
}
于 2008-09-05T13:25:27.430 に答える
2

LinFu を試してみてください。これには、実行時に任意のイベントにバインドできるユニバーサル イベント ハンドラがあります。たとえば、動的ボタンの Click イベントにハンドラーをバインドする方法は次のとおりです。

// 注: CustomDelegate 署名は次のように定義されます。
// パブリック デリゲート オブジェクト CustomDelegate(params object[] args);
CustomDelegate ハンドラー = デリゲート
                         {
                           Console.WriteLine("ボタンがクリックされました!");
                           null を返します。
                         };

ボタン myButton = new Button();
// ハンドラーをイベントに接続します
EventBinder.BindToEvent("クリック", myButton, ハンドラ);

LinFu では、デリゲート シグネチャに関係なく、ハンドラーを任意のイベントにバインドできます。楽しみ!

ここで見つけることができます: http://www.codeproject.com/KB/cs/LinFuPart3.aspx

于 2008-12-18T05:16:40.797 に答える
1

私は最近、ユニット テスト イベントについて説明した一連のブログ記事を書きました。ここで説明する手法の 1 つは、動的イベント サブスクリプションについてです。動的な側面にリフレクションと MSIL (コード エミッティング) を使用しましたが、これですべてうまくまとめられました。DynamicEvent クラスを使用すると、次のようにイベントを動的にサブスクライブできます。

EventPublisher publisher = new EventPublisher();

foreach (EventInfo eventInfo in publisher.GetType().GetEvents())
{
    DynamicEvent.Subscribe(eventInfo, publisher, (sender, e, eventName) =>
    {
        Console.WriteLine("Event raised: " + eventName);
    });
}

私が実装したパターンの機能の 1 つは、イベント名をイベント ハンドラーの呼び出しに挿入することで、どのイベントが発生したかがわかるようにすることです。単体テストに非常に役立ちます。

このブログ記事は、イベント ユニット テスト手法を説明しているため非常に長くなっていますが、完全なソース コードとテストが提供されており、動的イベント サブスクリプションがどのように実装されたかについての詳細な説明は、前回の投稿で詳述されています。

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

于 2010-04-22T18:25:05.783 に答える
0

必要なことは、依存性注入を使用して達成できます。たとえば、Microsoft Composite UI app blockは、あなたが説明したことを正確に実行します

于 2008-09-05T13:21:21.700 に答える
-1

次のような意味ですか。

//reflect out the method to fire as a delegate
EventHandler eventDelegate = 
   ( EventHandler ) Delegate.CreateDelegate(
       typeof( EventHandler ),    //type of event delegate
       objectWithEventSubscriber, //instance of the object with the matching method
       eventSubscriberMethodName, //the name of the method
       true );

これはサブスクリプションを行いませんが、呼び出すメソッドに渡します。

編集:

この回答の後に投稿が明確になりました。タイプがわからない場合、私の例は役に立ちません。

ただし、.Net のすべてのイベントはデフォルトのイベント パターンに従う必要があるため、それに従っている限り、これは基本的な EventHandler で機能します。

于 2008-09-05T13:25:10.413 に答える