2008-08-29 14 views
7

के माध्यम से किसी ईवेंट की पहचान करें संकलक आमतौर पर += या -= के बगल में कोई ईवेंट नहीं दिखाई देता है, इसलिए मुझे यकीन नहीं है कि यह संभव है या नहीं।एक लिंक अभिव्यक्ति वृक्ष

मैं एक अभिव्यक्ति वृक्ष का उपयोग कर एक घटना की पहचान करने में सक्षम होना चाहता हूं, इसलिए मैं एक परीक्षण के लिए एक ईवेंट वॉचर बना सकता हूं। वाक्य रचना कुछ इस तरह दिखेगा:

using(var foo = new EventWatcher(target, x => x.MyEventToWatch) { 
    // act here 
} // throws on Dispose() if MyEventToWatch hasn't fired 

मेरे सवालों का दुगना कर रहे हैं:

    विल
  1. संकलक चोक? और यदि हां, तो इसे रोकने के तरीके पर कोई सुझाव?
  2. target की MyEventToWatch घटना से संलग्न करने के लिए मैं कन्स्ट्रक्टर से अभिव्यक्ति ऑब्जेक्ट को कैसे पार्स कर सकता हूं?

उत्तर

4

संपादित करें:Curt के रूप में बताया गया है, मेरे कार्यान्वयन नहीं बल्कि में त्रुटिपूर्ण है कि यह केवल वर्ग है कि घटना वाणी के भीतर से इस्तेमाल किया जा सकता :) "x => x.MyEvent" घटना लौटने की बजाय, यह समर्थन लौट रहा था क्षेत्र, जो कक्षा द्वारा केवल अभिगम है।

चूंकि अभिव्यक्तियों में असाइनमेंट स्टेटमेंट नहीं हो सकते हैं, इसलिए "(x, h) => x.MyEvent += h" जैसी संशोधित अभिव्यक्ति का उपयोग ईवेंट को पुनर्प्राप्त करने के लिए नहीं किया जा सकता है, इसलिए इसके बजाय प्रतिबिंब का उपयोग करने की आवश्यकता होगी। एक सही कार्यान्वयन को घटना के लिए EventInfo पुनर्प्राप्त करने के लिए प्रतिबिंब का उपयोग करने की आवश्यकता होगी (जो दुर्भाग्यवश, दृढ़ता से टाइप नहीं किया जाएगा)।

अन्यथा, केवल अपडेट किए जाने की जरूरत है कि परिलक्षित EventInfo की दुकान, और (मैनुअल DelegateCombine/Remove कॉल और क्षेत्र सेट के बजाय) श्रोता पंजीकरण के लिए उपयोग AddEventHandler/RemoveEventHandler तरीकों कर रहे हैं। शेष कार्यान्वयन को बदलने की जरूरत नहीं है। गुड लक :)


नोट: इस प्रदर्शन गुणवत्ता वाले कोड एक्सेसर के स्वरूप के बारे में कई मान्यताओं बनाता है।

try { 
    using(EventWatcher.Create(o, x => x.MyEvent)) { 
    //o.RaiseEvent(); // Uncomment for test to succeed. 
    } 
    Console.WriteLine("Event raised successfully"); 
} 
catch(InvalidOperationException ex) { 
    Console.WriteLine(ex.Message); 
} 
: आदेश प्रकार निष्कर्ष का लाभ लेने के लिए)

public sealed class EventWatcher : IDisposable { 
    private readonly object target_; 
    private readonly string eventName_; 
    private readonly FieldInfo eventField_; 
    private readonly Delegate listener_; 
    private bool eventWasRaised_; 

    public static EventWatcher Create<T>(T target, Expression<Func<T,Delegate>> accessor) { 
    return new EventWatcher(target, accessor); 
    } 

    private EventWatcher(object target, LambdaExpression accessor) { 
    this.target_ = target; 

    // Retrieve event definition from expression. 
    var eventAccessor = accessor.Body as MemberExpression; 
    this.eventField_ = eventAccessor.Member as FieldInfo; 
    this.eventName_ = this.eventField_.Name; 

    // Create our event listener and add it to the declaring object's event field. 
    this.listener_ = CreateEventListenerDelegate(this.eventField_.FieldType); 
    var currentEventList = this.eventField_.GetValue(this.target_) as Delegate; 
    var newEventList = Delegate.Combine(currentEventList, this.listener_); 
    this.eventField_.SetValue(this.target_, newEventList); 
    } 

    public void SetEventWasRaised() { 
    this.eventWasRaised_ = true; 
    } 

    private Delegate CreateEventListenerDelegate(Type eventType) { 
    // Create the event listener's body, setting the 'eventWasRaised_' field. 
    var setMethod = typeof(EventWatcher).GetMethod("SetEventWasRaised"); 
    var body = Expression.Call(Expression.Constant(this), setMethod); 

    // Get the event delegate's parameters from its 'Invoke' method. 
    var invokeMethod = eventType.GetMethod("Invoke"); 
    var parameters = invokeMethod.GetParameters() 
     .Select((p) => Expression.Parameter(p.ParameterType, p.Name)); 

    // Create the listener. 
    var listener = Expression.Lambda(eventType, body, parameters); 
    return listener.Compile(); 
    } 

    void IDisposable.Dispose() { 
    // Remove the event listener. 
    var currentEventList = this.eventField_.GetValue(this.target_) as Delegate; 
    var newEventList = Delegate.Remove(currentEventList, this.listener_); 
    this.eventField_.SetValue(this.target_, newEventList); 

    // Ensure event was raised. 
    if(!this.eventWasRaised_) 
     throw new InvalidOperationException("Event was not raised: " + this.eventName_); 
    } 
} 

प्रयोग से कुछ भिन्न है का सुझाव दिया,; उचित त्रुटि जाँच, स्थिर घटनाओं, आदि के रख-रखाव, पाठक के लिए एक व्यायाम के रूप में छोड़ दिया जाता है

2

एक .NET घटना वास्तव में एक वस्तु नहीं है, यह एक अंतराल है जो दो कार्यों द्वारा दर्शाया जाता है - एक जोड़ने के लिए और एक हैंडलर को हटाने के लिए। यही कारण है कि संकलक आपको + = (जो जोड़ का प्रतिनिधित्व करता है) के अलावा कुछ भी नहीं करने देगा - = = (जो निकालने का प्रतिनिधित्व करता है)।

मेटाप्रोग्रामिंग उद्देश्यों के लिए किसी ईवेंट को संदर्भित करने का एकमात्र तरीका सिस्टम के रूप में है। Reflection.EventInfo, और प्रतिबिंब शायद एक का अधिकार पाने के लिए सबसे अच्छा तरीका है (यदि एकमात्र तरीका नहीं है)।

संपादित करें: सम्राट XLII कुछ खूबसूरत कोड है जो अपने स्वयं के ईवेंट के लिए काम करना चाहिए, बशर्ते आप उन्हें सी # से घोषित किया है लिखा है

public event DelegateType EventName; 

ऐसा इसलिए है क्योंकि सी # उस घोषणा से आप के लिए दो बातें बनाता है बस के रूप में:

  1. एक निजी प्रतिनिधि क्षेत्र घटना
  2. कार्यान्वयन कोड के साथ वास्तविक घटना के लिए समर्थन के रूप में सेवा करने के लिए भंडारण कि प्रतिनिधि के का उपयोग करता है।

सुविधाजनक रूप से, इनमें से दोनों का एक ही नाम है। यही कारण है कि नमूना कोड आपके स्वयं के कार्यक्रमों के लिए काम करेगा।

हालांकि, आप अन्य पुस्तकालयों द्वारा लागू कार्यक्रमों का उपयोग करते समय इस मामले पर भरोसा नहीं कर सकते हैं। विशेष रूप से, विंडोज फॉर्म और डब्ल्यूपीएफ में होने वाली घटनाओं का अपना बैकिंग स्टोरेज नहीं होता है, इसलिए नमूना कोड उनके लिए काम नहीं करेगा।

1

जबकि सम्राट XLII ने इसके लिए पहले ही जवाब दिया था, मैंने सोचा था कि इस के मेरे पुनर्लेख को साझा करने के दौरान यह मूल्यवान था। अफसोस की बात है, अभिव्यक्ति वृक्ष के माध्यम से घटना प्राप्त करने की कोई क्षमता नहीं है, मैं घटना का नाम उपयोग कर रहा हूं।

public sealed class EventWatcher : IDisposable { 
    private readonly object _target; 
    private readonly EventInfo _eventInfo; 
    private readonly Delegate _listener; 
    private bool _eventWasRaised; 

    public static EventWatcher Create<T>(T target, string eventName) { 
     EventInfo eventInfo = typeof(T).GetEvent(eventName); 
     if (eventInfo == null) 
      throw new ArgumentException("Event was not found.", eventName); 
     return new EventWatcher(target, eventInfo); 
    } 

    private EventWatcher(object target, EventInfo eventInfo) { 
     _target = target; 
     _eventInfo = event; 
     _listener = CreateEventDelegateForType(_eventInfo.EventHandlerType); 
     _eventInfo.AddEventHandler(_target, _listener); 
    } 

    // SetEventWasRaised() 
    // CreateEventDelegateForType 

    void IDisposable.Dispose() { 
     _eventInfo.RemoveEventHandler(_target, _listener); 
     if (!_eventWasRaised) 
      throw new InvalidOperationException("event was not raised."); 
    } 
} 

और उपयोग है:

using(EventWatcher.Create(o, "MyEvent")) { 
    o.RaiseEvent(); 
} 
3

मैं भी यह करने के लिए करना चाहता था, और मैं एक बहुत अच्छा तरीका है कि सम्राट XLII विचार की तरह कुछ करता है के साथ आए हैं। हालांकि अभिव्यक्ति पेड़ों का उपयोग नहीं करता है, जैसा कि बताया गया है कि यह अभिव्यक्ति पेड़ के रूप में नहीं किया जा सकता है += या -= के उपयोग की अनुमति नहीं देता है।

हम हालांकि एक साफ चाल का उपयोग कर सकते हैं जहां हम .NET Remoting Proxy (या किसी भी अन्य प्रॉक्सी जैसे कि लिनफू या कैसल डीपी) का उपयोग करते हैं ताकि एक बहुत ही कम जीवित प्रॉक्सी ऑब्जेक्ट पर हैंडलर जोड़ें/निकालें। इस प्रॉक्सी ऑब्जेक्ट की भूमिका केवल उस पर कुछ विधि कहा जाता है, और इसकी विधि को अवरुद्ध करने की अनुमति देने के लिए, जिस बिंदु पर हम ईवेंट का नाम ढूंढ सकते हैं।

यह अजीब लगता है, लेकिन यहाँ कोड (जो वैसे ही काम करता है अगर आप एक MarshalByRefObject या प्रॉक्सी में वस्तु के लिए एक इंटरफेस है)

मान लें हम निम्नलिखित इंटरफेस और वर्ग है

public interface ISomeClassWithEvent { 
    event EventHandler<EventArgs> Changed; 
} 


public class SomeClassWithEvent : ISomeClassWithEvent { 
    public event EventHandler<EventArgs> Changed; 

    protected virtual void OnChanged(EventArgs e) { 
     if (Changed != null) 
      Changed(this, e); 
    } 
} 

फिर हमारे पास एक बहुत ही सरल वर्ग हो सकता है जो Action<T> प्रतिनिधि से अपेक्षा करता है जो T का कुछ उदाहरण पारित करेगा।

यहाँ कोड

public class EventWatcher<T> { 
    public void WatchEvent(Action<T> eventToWatch) { 
     CustomProxy<T> proxy = new CustomProxy<T>(InvocationType.Event); 
     T tester = (T) proxy.GetTransparentProxy(); 
     eventToWatch(tester); 

     Console.WriteLine(string.Format("Event to watch = {0}", proxy.Invocations.First())); 
    } 
} 

चाल प्रदान की Action<T> प्रतिनिधि के लिए प्रॉक्सी वस्तु पारित करने के लिए है।

कहाँ हम निम्नलिखित CustomProxy<T> कोड है, जो उसे प्रॉक्सी वस्तु

public enum InvocationType { Event } 

public class CustomProxy<T> : RealProxy { 
    private List<string> invocations = new List<string>(); 
    private InvocationType invocationType; 

    public CustomProxy(InvocationType invocationType) : base(typeof(T)) { 
     this.invocations = new List<string>(); 
     this.invocationType = invocationType; 
    } 

    public List<string> Invocations { 
     get { 
      return invocations; 
     } 
    } 

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)] 
    [DebuggerStepThrough] 
    public override IMessage Invoke(IMessage msg) { 
     String methodName = (String) msg.Properties["__MethodName"]; 
     Type[] parameterTypes = (Type[]) msg.Properties["__MethodSignature"]; 
     MethodBase method = typeof(T).GetMethod(methodName, parameterTypes); 

     switch (invocationType) { 
      case InvocationType.Event: 
       invocations.Add(ReplaceAddRemovePrefixes(method.Name)); 
       break; 
      // You could deal with other cases here if needed 
     } 

     IMethodCallMessage message = msg as IMethodCallMessage; 
     Object response = null; 
     ReturnMessage responseMessage = new ReturnMessage(response, null, 0, null, message); 
     return responseMessage; 
    } 

    private string ReplaceAddRemovePrefixes(string method) { 
     if (method.Contains("add_")) 
      return method.Replace("add_",""); 
     if (method.Contains("remove_")) 
      return method.Replace("remove_",""); 
     return method; 
    } 
} 

पर += और -= करने के लिए कॉल को बीच में रोक दिया है और फिर हम सब अब सिर्फ़ रूप

class Program { 
    static void Main(string[] args) { 
     EventWatcher<ISomeClassWithEvent> eventWatcher = new EventWatcher<ISomeClassWithEvent>(); 
     eventWatcher.WatchEvent(x => x.Changed += null); 
     eventWatcher.WatchEvent(x => x.Changed -= null); 
     Console.ReadLine(); 
    } 
} 

इस प्रकार इस का उपयोग करने के लिए है ऐसा करने से मुझे यह आउटपुट दिखाई देगा:

Event to watch = Changed 
Event to watch = Changed 
संबंधित मुद्दे