क्या घटना की घोषणा पर एक अनाम खाली प्रतिनिधि को जोड़ने के लिए एक नकारात्मक पक्ष है?


82

मैंने इस मुहावरे के कुछ उल्लेख देखे हैं ( एसओ पर ):

// Deliberately empty subscriber
public event EventHandler AskQuestion = delegate {};

उल्टा स्पष्ट है - यह घटना को बढ़ाने से पहले अशक्त की जांच करने की आवश्यकता से बचा जाता है।

हालांकि, मैं समझने के लिए उत्सुक हूं कि क्या कोई डाउनसाइड है। उदाहरण के लिए, क्या यह ऐसा कुछ है जो व्यापक उपयोग में है और यह पर्याप्त पारदर्शी है कि यह एक रखरखाव सिरदर्द का कारण नहीं होगा? क्या खाली ईवेंट सब्सक्राइबर कॉल की कोई सराहनीय प्रदर्शन हिट है?

जवाबों:


36

केवल नकारात्मक पहलू यह है कि आप अतिरिक्त खाली प्रतिनिधि को बुला रहे हैं। इसके अलावा कोई मेंटेनेंस पेनल्टी या अन्य कमी नहीं है।


19
यदि घटना में अन्यथा बिल्कुल एक ग्राहक (एक बहुत ही सामान्य मामला) होगा, तो डमी हैंडलर इसे दो बना देगा। एक हैंडलर वाली घटनाओं को दो की तुलना में अधिक कुशलता से नियंत्रित किया जाता है
सुपरकैट

46

ओवरहेड प्रदर्शन को प्रेरित करने के बजाय, दोनों समस्याओं को कम करने के लिए एक विस्तार विधि का उपयोग क्यों न करें :

public static void Raise(this EventHandler handler, object sender, EventArgs e)
{
    if(handler != null)
    {
        handler(sender, e);
    }
}

एक बार परिभाषित होने के बाद, आपको फिर से एक और अशक्त घटना जांचने की आवश्यकता नहीं है:

// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);

2
जेनेरिक संस्करण के लिए यहां देखें: stackoverflow.com/questions/192980/…
बेंजोल

1
वास्तव में मेरी सहायक ट्रिनिटी लाइब्रेरी क्या करती है, संयोग से: thehelpertrinity.codeplex.com
Kent Boogaart

3
क्या यह केवल आपके विस्तार विधि में अशक्त जांच की थ्रेड समस्या को नहीं बढ़ाता है?
पैट्रिक वीवी

6
सं। हैंडलर विधि में पारित किया जाता है, जिस बिंदु पर, उस उदाहरण को संशोधित नहीं किया जा सकता है।
जुदा गेब्रियल हिमंगो

@PatrickV नहीं, यहूदा सही है, handlerऊपर पैरामीटर विधि के लिए एक मान पैरामीटर है। विधि के चलने के दौरान यह नहीं बदलेगा। ऐसा होने के लिए, इसे एक refपैरामीटर होने की आवश्यकता होगी (जाहिर है कि यह पैरामीटर के लिए दोनों refऔर thisसंशोधक के लिए अनुमति नहीं है ), या पाठ्यक्रम का एक क्षेत्र।
जेपी स्टिग नीलसन

42

ऐसी प्रणालियों के लिए जो घटनाओं का भारी उपयोग करती हैं और प्रदर्शन-महत्वपूर्ण हैं , आप निश्चित रूप से कम से कम यह नहीं करने पर विचार करना चाहेंगे । एक खाली प्रतिनिधि के साथ एक घटना को बढ़ाने के लिए लागत लगभग दो बार है कि इसे पहले एक अशक्त जांच के साथ बढ़ाने के लिए।

मेरी मशीन पर बेंचमार्क चलाने के कुछ आंकड़े इस प्रकार हैं:

For 50000000 iterations . . .
No null check (empty delegate attached): 530ms
With null check (no delegates attached): 249ms
With null check (with delegate attached): 452ms

और यहाँ इन आंकड़ों को प्राप्त करने के लिए कोड का उपयोग किया गया है:

using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        public event EventHandler<EventArgs> EventWithDelegate = delegate { };
        public event EventHandler<EventArgs> EventWithoutDelegate;

        static void Main(string[] args)
        {
            //warm up
            new Program().DoTimings(false);
            //do it for real
            new Program().DoTimings(true);

            Console.WriteLine("Done");
            Console.ReadKey();
        }

        private void DoTimings(bool output)
        {
            const int iterations = 50000000;

            if (output)
            {
                Console.WriteLine("For {0} iterations . . .", iterations);
            }

            //with anonymous delegate attached to avoid null checks
            var stopWatch = Stopwatch.StartNew();

            for (var i = 0; i < iterations; ++i)
            {
                RaiseWithAnonDelegate();
            }

            stopWatch.Stop();

            if (output)
            {
                Console.WriteLine("No null check (empty delegate attached): {0}ms", stopWatch.ElapsedMilliseconds);
            }


            //without any delegates attached (null check required)
            stopWatch = Stopwatch.StartNew();

            for (var i = 0; i < iterations; ++i)
            {
                RaiseWithoutAnonDelegate();
            }

            stopWatch.Stop();

            if (output)
            {
                Console.WriteLine("With null check (no delegates attached): {0}ms", stopWatch.ElapsedMilliseconds);
            }


            //attach delegate
            EventWithoutDelegate += delegate { };


            //with delegate attached (null check still performed)
            stopWatch = Stopwatch.StartNew();

            for (var i = 0; i < iterations; ++i)
            {
                RaiseWithoutAnonDelegate();
            }

            stopWatch.Stop();

            if (output)
            {
                Console.WriteLine("With null check (with delegate attached): {0}ms", stopWatch.ElapsedMilliseconds);
            }
        }

        private void RaiseWithAnonDelegate()
        {
            EventWithDelegate(this, EventArgs.Empty);
        }

        private void RaiseWithoutAnonDelegate()
        {
            var handler = EventWithoutDelegate;

            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }
}

11
तुम मज़ाक कर रहे, है ना? आह्वान 5 नैनोसेकंड जोड़ता है और आप इसे करने के खिलाफ चेतावनी दे रहे हैं? मैं उस से अधिक अनुचित सामान्य अनुकूलन के बारे में नहीं सोच सकता।
ब्रैड विल्सन

8
दिलचस्प। आपके निष्कर्षों के अनुसार, अशक्त के लिए जाँच करना और जाँच के बिना केवल एक प्रतिनिधि को बुलाना अधिक तेज़ है। मेरे लिए सही नहीं लगता। लेकिन वैसे भी यह इतना छोटा अंतर है कि मुझे नहीं लगता कि यह सभी मामलों में ध्यान देने योग्य है।
मौरिस

22
ब्रैड, मैंने विशेष रूप से प्रदर्शन-महत्वपूर्ण प्रणालियों के लिए कहा जो घटनाओं का भारी उपयोग करते हैं। वह सामान्य कैसे है?
केंट बोगार्ट

3
आप खाली प्रतिनिधियों को कॉल करते समय प्रदर्शन दंड के बारे में बात कर रहे हैं। मैं आपसे पूछता हूं: क्या होता है जब कोई वास्तव में घटना की सदस्यता लेता है? आपको खाली प्रतिनिधियों के नहीं, ग्राहकों के प्रदर्शन के बारे में चिंता करनी चाहिए।
लिवियू ट्रिफ़ोई

2
बड़ी प्रदर्शन लागत उस मामले में नहीं आती है जहां शून्य "वास्तविक" ग्राहक हैं, लेकिन इस मामले में जहां एक है। उस स्थिति में, सदस्यता, सदस्यता रद्द और आह्वान बहुत कुशल होना चाहिए क्योंकि उन्हें मंगलाचरण सूचियों के साथ कुछ भी नहीं करना होगा, लेकिन यह तथ्य कि घटना (सिस्टम के नजरिए से) के पास दो ग्राहक होंगे बजाय एक का उपयोग करने के लिए मजबूर करेंगे कोड जो "n" ग्राहकों को संभालता है, कोड के बजाय जो "शून्य" या "एक" मामले के लिए अनुकूलित है।
सुपरकैट

7

यदि आप इसे / बहुत / कर रहे हैं, तो आप एक एकल, स्थिर / साझा खाली प्रतिनिधि चाहते हैं जिसे आप पुन: उपयोग करते हैं, बस प्रतिनिधि प्रतिनिधि की मात्रा को कम करने के लिए। ध्यान दें कि कंपाइलर इस डेलीगेट को प्रति इवेंट (किसी स्थिर क्षेत्र में) वैसे भी कैश करता है, इसलिए यह ईवेंट परिभाषा के अनुसार केवल एक डेलिगेट उदाहरण है, इसलिए यह बहुत बड़ी बचत नहीं है - लेकिन शायद सार्थक है।

प्रत्येक कक्षा में प्रति-आवृत्ति क्षेत्र अभी भी एक ही स्थान लेगा, निश्चित रूप से।

अर्थात

internal static class Foo
{
    internal static readonly EventHandler EmptyEvent = delegate { };
}
public class Bar
{
    public event EventHandler SomeEvent = Foo.EmptyEvent;
}

इसके अलावा, यह ठीक लगता है।


1
यह यहाँ से उत्तर लगता है: stackoverflow.com/questions/703014/… कि संकलक पहले से ही एक उदाहरण के लिए अनुकूलन करता है।
शासन

3

कुछ चरम स्थितियों के लिए, इसके अलावा, संभवतः के बारे में बात करने के लिए कोई सार्थक प्रदर्शन दंड नहीं है।

ध्यान दें, हालांकि, यह ट्रिक C # 6.0 में कम प्रासंगिक हो जाती है, क्योंकि भाषा प्रतिनिधियों को कॉल करने के लिए एक वैकल्पिक सिंटैक्स प्रदान करती है, जो निम्न हो सकता है:

delegateThatCouldBeNull?.Invoke(this, value);

ऊपर, अशक्त सशर्त संचालक ?.एक सशर्त आह्वान के साथ अशक्त जाँच को जोड़ती है।


2

यह मेरी समझ है कि खाली प्रतिनिधि थ्रेड सुरक्षित है, जबकि अशक्त चेक नहीं है।


2
यहां दिए गए उत्तर में बताए अनुसार न तो पैटर्न घटना सूत्र को सुरक्षित बनाते हैं: stackoverflow.com/questions/10282043/…
Beachwalker

2

मैं कहूंगा कि यह एक खतरनाक निर्माण है, क्योंकि यह आपको कुछ ऐसा करने के लिए प्रेरित करता है:

MyEvent(this, EventArgs.Empty);

यदि क्लाइंट कोई अपवाद फेंकता है, तो सर्वर इसके साथ जाता है।

तो, शायद आप करते हैं:

try
{
  MyEvent(this, EventArgs.Empty);
}
catch
{
}

लेकिन, यदि आपके पास कई ग्राहक हैं और एक ग्राहक एक अपवाद फेंकता है, तो अन्य ग्राहकों के साथ क्या होता है?

उस अंत तक, मैं कुछ स्थैतिक सहायक विधियों का उपयोग कर रहा हूं जो अशक्त जांच करते हैं और ग्राहक की ओर से किसी भी अपवाद को निगलते हैं (यह आइडियल से है)।

// Usage
EventHelper.Fire(MyEvent, this, EventArgs.Empty);


public static void Fire(EventHandler del, object sender, EventArgs e)
{
    UnsafeFire(del, sender, e);
}
private static void UnsafeFire(Delegate del, params object[] args)
{
    if (del == null)
    {
        return;
    }
    Delegate[] delegates = del.GetInvocationList();

    foreach (Delegate sink in delegates)
    {
        try
        {
            sink.DynamicInvoke(args);
        }
        catch
        { }
    }
}

नाइटपिक के लिए नहीं, लेकिन न तो आप बात <कोड> अगर (डेल == नल) {वापसी; } प्रतिनिधि [] प्रतिनिधियों = del.GetInvocationList ()? </ Code> एक दौड़ हालत उम्मीदवार है?
चेरियन

काफी नहीं। चूंकि प्रतिनिधि मूल्य प्रकार हैं, डेल वास्तव में प्रतिनिधि श्रृंखला की एक निजी प्रति है जो केवल असुरक्षित विधि निकाय के लिए सुलभ है। (चेतावनी: यह विफल रहता है UnsafeFire inlined हो जाता है, तो आप इसके खिलाफ हित के लिए [MethodImpl (MethodImplOptions.NoInlining)] विशेषता का उपयोग करने की आवश्यकता होगी।)
डैनियल Fortunov

1) प्रतिनिधि संदर्भ प्रकार 2 हैं) वे अपरिवर्तनीय हैं, इसलिए यह एक दौड़ की स्थिति नहीं है 3) मुझे नहीं लगता कि inlining इस कोड के व्यवहार को नहीं बदलता है। मुझे उम्मीद है कि जब पैरामीटर इनलाइन एक नया स्थानीय चर बन जाता है।
कोडइन्चोस

'अपवाद छोड़ दिए जाने पर क्या होता है' का आपका समाधान अपवादों को अनदेखा करना है? यही कारण है कि मैं हमेशा या लगभग हमेशा एक कोशिश में सभी घटना सदस्यता को लपेटता हूं (एक आसान Try.TryAction()फ़ंक्शन का उपयोग करते हुए , एक स्पष्ट try{}ब्लॉक नहीं), लेकिन मैं अपवादों को अनदेखा नहीं करता, मैं उन्हें रिपोर्ट करता हूं ...
डेव कज़िनो

0

"खाली डेलीगेट" दृष्टिकोण के बजाय, एक साधारण विस्तार विधि को परिभाषित कर सकता है, जो नल के खिलाफ ईवेंट हैंडलर की जाँच करने की पारंपरिक विधि को एनकैप्सुलेट कर सकता है। यह यहाँ और यहाँ वर्णित है


-2

इस प्रश्न के उत्तर के रूप में अब तक एक बात याद आती है: शून्य मान के लिए चेक से बचना खतरनाक है

public class X
{
    public delegate void MyDelegate();
    public MyDelegate MyFunnyCallback = delegate() { }

    public void DoSomething()
    {
        MyFunnyCallback();
    }
}


X x = new X();

x.MyFunnyCallback = delegate() { Console.WriteLine("Howdie"); }

x.DoSomething(); // works fine

// .. re-init x
x.MyFunnyCallback = null;

// .. continue
x.DoSomething(); // crashes with an exception

बात यह है: आप कभी नहीं जानते कि कौन आपके कोड का उपयोग किस तरीके से करेगा। आप कभी नहीं जानते हैं, यदि आपके कोड के बग फिक्स के दौरान कुछ वर्षों में घटना / हैंडलर शून्य पर सेट है।

हमेशा, अगर जाँच लिखो।

उम्मीद है की वो मदद करदे ;)

ps: प्रदर्शन गणना के लिए धन्यवाद।

pps: इसे एक घटना के मामले से और कॉलबैक उदाहरण में संपादित किया। प्रतिक्रिया के लिए धन्यवाद ... मैंने उदाहरण w / o विज़ुअल स्टूडियो को "कोडित" किया और एक घटना को ध्यान में रखते हुए मैंने उस उदाहरण को समायोजित किया। गलतफहमी के लिए खेद है।

ppps: नहीं पता कि क्या यह अभी भी धागे पर फिट बैठता है ... लेकिन मुझे लगता है कि यह एक महत्वपूर्ण सिद्धांत है। कृपया स्टैकफ़्लो का एक और धागा भी जांचें


1
x.yFunnyEvent = null; <- वह भी (कक्षा के बाहर) संकलन नहीं करता है। एक घटना का बिंदु केवल + = और - = का समर्थन कर रहा है। और आप x.MyFunnyEvent- = x.MyFunnyEvent को क्लास से बाहर भी नहीं कर सकते क्योंकि इवेंट गेट्टर क्वैसी है protected। आप केवल कक्षा (या एक व्युत्पन्न वर्ग) से घटना को तोड़ सकते हैं।
कोडइन्चोस

आप सही हैं ... घटनाओं के लिए सही है ... एक साधारण हैंडलर के साथ मामला था। माफ़ करना। मैं संपादित करने की कोशिश करूंगा।
थॉमस

बेशक अगर आपका प्रतिनिधि सार्वजनिक है जो खतरनाक होगा क्योंकि आप कभी नहीं जानते कि उपयोगकर्ता क्या करेगा। हालांकि, यदि आप एक निजी चर पर खाली प्रतिनिधियों को सेट करते हैं और आप + = और - = अपने आप को संभालते हैं, तो यह समस्या नहीं होगी और अशक्त चेक थ्रेड सुरक्षित होगा।
ForceMagic
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.