2012-10-26 36 views
5

का उपयोग कर सी # घटनाओं के लिए गतिशील बाध्यकारी मेरे पास बहुत खराब दस्तावेज के साथ एक WPF नियंत्रण है।प्रतिबिंब

कोडबींड में मैं उन घटनाओं पर प्रतिबिंबित करना चाहता हूं जो नियंत्रण 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<> सही प्रकार के।

आपको ईवेंट के प्रतिनिधि प्रकार प्रतिबिंबित (हथियाने यह Invoke पद्धति के रूप में @Davio ने उल्लेख किया है) सभी तर्कों बाहर निकालते हैं और लैम्ब्डा विधि के लिए आपूर्ति करने के लिए उन लोगों में से प्रत्येक के लिए ParameterExpression रों बनाने के लिए।

यहाँ एक पूर्ण समाधान है कि आप एक मानक इकाई परीक्षण में चिपका सकते हैं, मैं एक अनुवर्ती संपादन में बाद में समझाने होगी:

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 सदस्य संकलक द्वारा संकलित किया गया है जो किसी भी हैंडलर के हस्ताक्षर को प्रतिबिंबित करता है जो इसे बाध्य किया जा सकता है। इसलिए, हम उस विधि को प्रतिबिंबित करते हैं और इसके लिए कोई भी पैरामीटर प्राप्त करते हैं, प्रत्येक के लिए लिंक ParameterExpression उदाहरण बनाते हैं। पैरामीटर नामकरण एक अच्छी तरह से है, यह यहां मायने रखता है।

फिर हम एक एकल लाइन लैम्ब्डा, जिनके शरीर मूल रूप से है निर्माण:

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

(यहाँ ध्यान दें कि ईवेंट के नाम कोड के रूप में जहाँ तक गतिशील कोड में खींच लिया और एक निरंतर स्ट्रिंग में शामिल किया जाता है चिंतित)

जब हम लैम्ब्डा का निर्माण, हम भी Expression.Lambda विधि प्रतिनिधि के प्रकार हम बनाने का इरादा (घटना के प्रतिनिधि प्रकार) के लिए सीधे बाध्य बताओ, और है कि हम से पहले बनाए गए ParameterExpression सरणी पारित करके है , यह एक विधि उत्पन्न करेगा जिसमें टी है टोपी कई मानकों। हम गतिशील कोड को वास्तव में संकलित करने के लिए Compile विधि का उपयोग करते हैं, जो हमें Delegate देता है जिसे हम AddEventHandler पर तर्क के रूप में उपयोग कर सकते हैं।

मैं ईमानदारी से आशा करता हूं कि यह बताता है कि हमने क्या किया है - यदि आपने अभिव्यक्तियों और गतिशील कोड के साथ काम नहीं किया है, इससे पहले कि यह दिमागी झुकाव हो। दरअसल, जिन लोगों के साथ मैं काम करता हूं उनमें से कुछ बस इस वूडू को बुलाते हैं। उदाहरण को

+0

यह दिलचस्प लगता है लेकिन मैं लागू करने के लिए संघर्ष कर रहा हूं! – Nick

+0

मैं आपके साथ उपयोग करने के लिए टाइप + और ईवेंट के साथ एक टेस्ट रखूंगा ... आधा घंटे लग सकता है ... –

+0

मुझे उस भाग में जाना है जहां मैं EventInfo ऑब्जेक्ट पर AddHandler को कॉल करता हूं और मुझे मिल रहा है: * "ऑब्जेक्ट ऑफ टाइप 'System.Action'2 [System.Object, System.Windows.Input.MouseButtonEventArgs]' को 'System.Windows.Input.MouseButtonEventHandler' प्रकार में परिवर्तित नहीं किया जा सकता है।" * – Nick