2012-10-26 75 views
5

我有一個非常差的文檔的WPF控件。動態綁定到C#事件使用反射

在代碼隱藏中,我想反映一下控件使用GetType().GetEVents()觸發的事件,併爲每個事件添加一個處理程序,它僅顯示事件的名稱。

這將允許我看到與控件實際上正在進行的交互。

到目前爲止,我有:

foreach (var e in GetType().GetEvents()) 
{ 
    var name = e.Name; 
    var handler = new Action<object,object>((o1,o2) =>Console.WriteLine(name)); 

    try 
    { 
     e.AddEventHandler(
        this, 
        Delegate.CreateDelegate(
           e.EventHandlerType, 
           handler.Target, 
           handler.Method 
           )); 
    } 
    catch (Exception ex) 
    { 
     Console.WriteLine("Failed to bind to event {0}", e.Name); 
    } 
} 

這似乎當事件簽名是(object,EventArgs)但未能在某些其他活動時,結合工作。

有沒有辦法做到這一點,而不必知道事件的簽名?

回答

6

您可以使用System.Linq.Expressions.Expression類來生成與事件簽名匹配的動態處理程序 - 只需撥打電話Console.WriteLine即可。

可以使用Expression.Lambda方法(提供了您需要的特定超載的鏈接)來生成正確類型的Func<>或更可能的Action<>

您反映事件的委託類型(抓取它的@Davio提到的Invoke方法)以提取所有參數併爲要提供給lambda方法的每個參數創建​​。

這裏,你可以粘貼到一個標準的單元測試一個完整的解決方案,我將在後續的編輯事後解釋:

public class TestWithEvents 
{ 
    //just using random delegate signatures here 
    public event Action Handler1; 
    public event Action<int, string> Handler2; 

    public void RaiseEvents(){ 
    if(Handler1 != null) 
     Handler1(); 
    if(Handler2 != null) 
     Handler2(0, "hello world"); 
    } 
} 

public static class DynamicEventBinder 
{ 
    public static Delegate GetHandler(System.Reflection.EventInfo ev) { 
    string name = ev.Name; 
    // create an array of ParameterExpressions 
    // to pass to the Expression.Lambda method so we generate 
    // a handler method with the correct signature. 
    var parameters = ev.EventHandlerType.GetMethod("Invoke").GetParameters(). 
     Select((p, i) => Expression.Parameter(p.ParameterType, "p" + i)).ToArray(); 

    // this and the Compile() can be turned into a one-liner, I'm just 
    // splitting it here so you can see the lambda code in the Console 
    // Note that we use the Event's type for the lambda, so it's tightly bound 
    // to that event. 
    var lambda = Expression.Lambda(ev.EventHandlerType, 
     Expression.Call(typeof(Console).GetMethod(
     "WriteLine", 
     BindingFlags.Public | BindingFlags.Static, 
     null, 
     new[] { typeof(string) }, 
     null), Expression.Constant(name + " was fired!")), parameters); 

    //spit the lambda out (for bragging rights) 
    Console.WriteLine(
     "Compiling dynamic lambda {0} for event \"{1}\"", lambda, name); 
    return lambda.Compile(); 
    } 

    //note - an unsubscribe might be handy - which would mean 
    //caching all the events that were subscribed for this object 
    //and the handler. Probably makes more sense to turn this type 
    //into an instance type that binds to a single object... 
    public static void SubscribeAllEvents(object o){ 
    foreach(var e in o.GetType().GetEvents()) 
    { 
     e.AddEventHandler(o, GetHandler(e)); 
    } 
    } 
} 

[TestMethod] 
public void TestSubscribe() 
{ 
    TestWithEvents testObj = new TestWithEvents(); 
    DynamicEventBinder.SubscribeAllEvents(testObj); 
    Console.WriteLine("Raising events..."); 
    testObj.RaiseEvents(); 
    //check the console output 
} 

大綱 - 我們開始有一些事件類型(我正在使用Action,但它應該可以處理任何事情),並且有一種方法可以用來測試所有那些擁有訂閱者的事件。

然後到DynamicEventBinder類,它有兩種方法:GetHandler - 爲特定類型的特定事件獲取處理程序;和,它爲那種類型的給定實例綁定所有這些事件 - 它簡單地遍歷所有事件,爲每個事件調用AddEventHandler,調用GetHandler來獲取處理程序。

GetHandler方法是肉和骨頭的地方 - 並且完全按照我在大綱中提出的方法。

委託類型有一個Invoke成員編譯到它的編譯器,它反映了它可以綁定到任何處理程序的簽名。因此,我們反映該方法並獲取它的任何參數,從而爲每個實例創建Linq​​實例。命名參數是一個很好的選擇,這裏的類型很重要。

然後我們建立了一個單行的λ,它的身體基本上是:

Console.WriteLine("[event_name] was fired!"); 

(這裏注意事件的名字就被拉入動態代碼,並納入一個常量字符串作爲密碼而言)

當我們建立拉姆達,我們還告訴Expression.Lambda方法委託的類型,我們打算建立(直接鍵合於委託類型的事件),並且通過使我們之前創建的​​陣列,它會產生一個有t的方法帽子很多參數。我們使用Compile方法來實際編譯動態代碼,它給了我們一個Delegate,然後我們可以將它用作參數AddEventHandler

我真誠地希望這解釋了我們所做的 - 如果您在表達式和動態代碼之前沒有使用過表達式和動態代碼,那麼它可能是令人頭痛的事情。事實上,我一起工作的一些人簡單地稱之爲巫術。

+0

這聽起來很有趣,但我很努力實施! – Nick

+0

我會把類型+和事件的測試放在一起給你使用......可能需要半個小時...... –

+0

我已經到了我在EventInfo對象上調用AddHandler的部分,並且我得到: *'System.Action'2類型的對象無法轉換爲'System.Windows.Input.MouseButtonEventHandler'類型。「* – Nick