2008-09-05 135 views
30

如何動態訂閱C#事件,以便給定一個Object實例和一個包含事件名稱的String名稱,您訂閱該事件並執行一些操作(例如寫入控制檯)該事件已被解僱?C#動態事件訂閱

它似乎使用反射這是不可能的,我想避免必須使用Reflection.Emit,如果可能的話,因爲目前(對我來說)似乎是唯一的方法。

/編輯:我不知道需要事件的委託的簽名,這是問題的核心

/EDIT 2:雖然委託逆變似乎是個不錯的計劃,我不能作出使用此解決方案的必要假設

回答

28

您可以編譯表達式樹來使用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(); 
    } 
} 
+0

地獄,表情樹太酷了。我通過Reflection.Emit寫了一次類似的代碼。多麼痛苦。 – 2009-02-11 04:07:13

+0

偉大的一段代碼。你能否展示如何改變它以支持參數?我改變了它以獲得帶有參數的方法,但是當我嘗試創建委託時,我得到了'範圍''引用的'System.String'類型的變量'x',但沒有定義'。謝謝 – 2012-01-23 12:38:31

2

有可能使用反射

var o = new SomeObjectWithEvent; 
o.GetType().GetEvent("SomeEvent").AddEventHandler(...); 
訂閱事件

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

現在,這裏將是你將不得不解決的問題。每個事件處理程序所需的委託將具有不同的簽名。您將不得不動態創建這些方法,這可能意味着Reflection.Emit,或者您將不得不將自己限制爲某個委託,以便您可以使用編譯代碼處理它。

希望這會有所幫助。

-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。

1
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 
} 
8

這不是一個完全通用的解決方案,但如果您的所有活動的形式是 無效美孚(對象o,T參數),其中T從EventArgs的派生,那麼你可以使用委託逆變逃脫它。像這樣的(其中的KeyDown的簽名是不一樣的點擊的):

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!"); 
    } 
2

嘗試李林甫 - 這是一個普遍的事件處理程序,可以讓你在運行時綁定到任何事件。例如,這裏是你可以處理程序綁定到一個動態按鈕的Click事件:

 
// Note: The CustomDelegate signature is defined as: 
// public delegate object CustomDelegate(params object[] args); 
CustomDelegate handler = delegate 
         { 
          Console.WriteLine("Button Clicked!"); 
          return null; 
         }; 

Button myButton = new Button(); 
// Connect the handler to the event 
EventBinder.BindToEvent("Click", myButton, handler); 

李林甫,您可以綁定你處理任何事件,無論是委託簽名的。請享用!

你可以在這裏找到: http://www.codeproject.com/KB/cs/LinFuPart3.aspx

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

一個我實現了模式的特點是,它注入事件名稱到調用事件處理程序,所以你知道哪個事件被提出。對單元測試非常有用。

的博客文章是相當漫長的,因爲它是描述事件的單元測試技術,但提供完整的源代碼和測試,以及如何動態事件訂閱已實施了詳細的描述是在最後發表的文章詳細。

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