2009-06-01 8 views
73

डुप्लिकेट झुका: How to ensure an event is only subscribed to once और Has an event handler already been added?सी # एक ईवेंट हैंडलर को रोकने के लिए पैटर्न के दो बार

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

उत्तर

120

घटना को स्पष्ट रूप से कार्यान्वित करें और आमंत्रण सूची की जांच करें। तुम भी अशक्त के लिए जाँच करने की आवश्यकता होगी:, एक फोन करने वाले घटना कई बार की सदस्यता लेता है, तो

using System.Linq; // Required for the .Contains call below: 

... 

private EventHandler foo; 
public event EventHandler Foo 
{ 
    add 
    { 
     if (foo == null || !foo.GetInvocationList().Contains(value)) 
     { 
      foo += value; 
     } 
    } 
    remove 
    { 
     foo -= value; 
    } 
} 

उपरोक्त कोड का उपयोग करना, यह केवल नजरअंदाज कर दिया जाएगा।

+13

आपको सिस्टम का उपयोग करने की आवश्यकता है। लिंक का उपयोग कर। –

+0

हरमन की टिप्पणी को स्पष्ट करने के लिए बस; आपको अपनी कक्षा या वर्तमान नामस्थान में 'System.Linq' का उपयोग करके नामस्थान 'System.Linq' शामिल करना होगा। –

+0

आकर्षक। LINQ अभी भी मेरे लिए काफी नया है कि मुझे इसे देखना है और याद दिलाया जाना चाहिए इसका मतलब है भाषा एकीकृत क्वेरी ... और फिर आश्चर्य है कि ईवेंट इन्डलैंडर्स और उनके आमंत्रण सूची के साथ क्या करना है? – fortboise

12

आपको ईवेंट पर एक्सेसर्स को जोड़ने और निकालने की आवश्यकता है, और उसके बाद प्रतिनिधि की लक्षित सूची देखें, या लक्ष्य को स्टोर करें सूची।

ऐड विधि में, आप प्रतिनिधि को पहले से जोड़े गए लक्ष्यों की एक सूची प्राप्त करने के लिए Delegate.GetInvocationList विधि का उपयोग कर सकते हैं।

चूंकि प्रतिनिधियों को समान लक्ष्य की तुलना करने के लिए परिभाषित किया जाता है, यदि वे एक ही लक्ष्य वस्तु से उसी विधि से जुड़े होते हैं, तो आप शायद उस सूची के माध्यम से भाग सकते हैं और तुलना कर सकते हैं, और यदि आपको कोई भी तुलना करने वाला कोई नहीं मिलता है, तो आप नया जोड़ते हैं ।

यहाँ नमूना कोड है, सांत्वना आवेदन के रूप में संकलन:

using System; 
using System.Linq; 

namespace DemoApp 
{ 
    public class TestClass 
    { 
     private EventHandler _Test; 

     public event EventHandler Test 
     { 
      add 
      { 
       if (_Test == null || !_Test.GetInvocationList().Contains(value)) 
        _Test += value; 
      } 

      remove 
      { 
       _Test -= value; 
      } 
     } 

     public void OnTest() 
     { 
      if (_Test != null) 
       _Test(this, EventArgs.Empty); 
     } 
    } 

    class Program 
    { 
     static void Main() 
     { 
      TestClass tc = new TestClass(); 
      tc.Test += tc_Test; 
      tc.Test += tc_Test; 
      tc.OnTest(); 
      Console.In.ReadLine(); 
     } 

     static void tc_Test(object sender, EventArgs e) 
     { 
      Console.Out.WriteLine("tc_Test called"); 
     } 
    } 
} 

आउटपुट:

tc_Test called 

(यानी केवल एक बार।)

+0

कृपया मेरी टिप्पणी को अनदेखा करें, मैं लिंक का उपयोग कर भूल गया। –

+0

स्वच्छतम समाधान (हालांकि सबसे छोटा नहीं)। – Shimmy

0

अपने सिंगलटन वस्तु की जांच यह यह जो सूचित करता है की सूची दी गई है है और डुप्लिकेट होने पर केवल एक बार कॉल करें। वैकल्पिक रूप से यदि संभव हो तो घटना अनुलग्नक अनुरोध को अस्वीकार कर दें।

17

आप वास्तव में सिंक स्तर और नहीं स्रोत स्तर पर इस संभाल चाहिए। यही है, ईवेंट स्रोत पर ईवेंट हैंडलर तर्क निर्धारित न करें - इसे हैंडलर (सिंक) पर छोड़ दें।

सेवा के डेवलपर के रूप में, आप कौन कहेंगे कि सिंक केवल एक बार पंजीकरण कर सकते हैं? क्या होगा यदि वे किसी कारण से दो बार पंजीकरण करना चाहते हैं? और यदि आप स्रोत को संशोधित करके सिंक में बग को सही करने का प्रयास कर रहे हैं, तो यह सिंक-स्तर पर इन समस्याओं को ठीक करने का एक अच्छा कारण है।

मुझे यकीन है कि आपके पास आपके कारण हैं; एक घटना स्रोत जिसके लिए डुप्लिकेट सिंक अवैध हैं, वह अतुलनीय नहीं है। लेकिन शायद आपको एक वैकल्पिक वास्तुकला पर विचार करना चाहिए जो किसी घटना के अर्थशास्त्र को बरकरार रखे।

+0

यह [इस समाधान] (http://stackoverflow.com/a/1104269/3367144) के लिए एक उत्कृष्ट इंजीनियरिंग औचित्य है, जो स्रोत के बजाय ईवेंट सिंक (ग्राहक/उपभोक्ता/पर्यवेक्षक/हैंडलर) पक्ष पर समस्या हल करता है पक्ष। – kdbanman

140

के बारे में सिर्फ घटना, -= के साथ पहली बार को हटाने अगर यह नहीं पाया जाता है एक अपवाद फेंका नहीं है

/// -= Removes the event if it has been already added, this prevents multiple firing of the event 
((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii); 
((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii); 
+0

धन्यवाद। यह एक ही परिदृश्य में बहुत आसान था (WebBrowser + HtmlElementEventHandler)। – Odys

+0

को इंगित करने के लिए धन्यवाद यह स्वीकार्य उत्तर होना चाहिए क्योंकि यह सरल है और इसके लिए कोई कस्टम कार्यान्वयन की आवश्यकता नहीं है। @LoxLox एक ही पैटर्न को कार्यान्वयन के रूप में भी दिखाता है। मैंने परीक्षण नहीं किया, इसलिए मैं उनके शब्द पर टिप्पणियां ले रहा हूं। बहुत अच्छा। – Rafe

+3

+1 मुझे लगता है कि यह प्रोग्रामर पर निर्भर है, लेकिन मैं कहूंगा कि यह सबसे अच्छा है (अंततः डेवलपर को ऐसा करने के लिए "सबसे अच्छे" की आवश्यकता नहीं है, लेकिन यह घटना जनरेटर की गलती नहीं है कि ग्राहक रोक नहीं सकता एकाधिक सब्सक्रिप्शन, इसलिए, उन्हें हटाने, इत्यादि को समझने के लिए, ... इसके अलावा, किसी को एक ही हैंडलर को एक से अधिक बार सब्सक्राइब करने से रोकने के लिए क्यों रोकें?) –

6

माइक्रोसॉफ्ट के Reactive Extensions (Rx) framework भी ऐसा करने के लिए "केवल एक बार सदस्यता ले" इस्तेमाल किया जा सकता कैसे।

एक माउस घटना foo.Clicked को देखते हुए यहां सदस्यता लें और केवल एक ही मंगलाचरण प्राप्त करने के लिए बताया गया है:

Observable.FromEvent<MouseEventArgs>(foo, "Clicked") 
    .Take(1) 
    .Subscribe(MyHandler); 

... 

private void MyHandler(IEvent<MouseEventArgs> eventInfo) 
{ 
    // This will be called just once! 
    var sender = eventInfo.Sender; 
    var args = eventInfo.EventArgs; 
} 

"एक बार सदस्यता ले" कार्यक्षमता प्रदान करने के अलावा, RX दृष्टिकोण घटनाओं को एक साथ रचना करने की क्षमता प्रदान करता है या घटनाओं को फ़िल्टर करें। यह काफी निफ्टी है।

+0

हालांकि यह तकनीकी रूप से सही है, लेकिन यह गलत प्रश्न का उत्तर देता है। – AlexFoxGill

0

चांदी की रोशनी में आपको ई। हैंडल = सत्य कहना होगा; घटना कोड में।

void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
{ 
    e.Handled = true; //this fixes the double event fire problem. 
    string name = (e.OriginalSource as Image).Tag.ToString(); 
    DoSomething(name); 
} 

अगर यह मदद करता है तो कृपया मुझे टिकटें।

1

किसी ईवेंट की बजाय कोई कार्रवाई बनाएं।

public class MyClass 
{ 
       // sender arguments  <-----  Use this action instead of an event 
    public Action<object, EventArgs> OnSomeEventOccured; 

    public void SomeMethod() 
    { 
      if(OnSomeEventOccured!=null) 
       OnSomeEventOccured(this, null); 
    } 

} 
20

मैं प्रत्येक समाधान और सबसे अच्छा एक (पर विचार प्रदर्शन) परीक्षण किया है है:: अपने वर्ग की तरह लग सकता है

private EventHandler _foo; 
public event EventHandler Foo { 

    add { 
     _foo -= value; 
     _foo += value; 
    } 
    remove { 
     _foo -= value; 
    } 
} 

नहीं Linq आवश्यक का उपयोग कर। सदस्यता रद्द करने से पहले शून्य की जांच करने की आवश्यकता नहीं है (विवरण के लिए एमएस इवेंट हैंडलर देखें)। हर जगह सदस्यता रद्द करने की याद रखने की जरूरत नहीं है।

संबंधित मुद्दे