Olay bildirimine anonim boş bir delege eklemenin bir dezavantajı var mı?


82

Bu deyimden birkaç kez bahsettiğini gördüm ( SO dahil ):

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

Tersi açıktır - olayı yükseltmeden önce boşluğu kontrol etme ihtiyacını ortadan kaldırır.

Bununla birlikte, herhangi bir dezavantaj olup olmadığını anlamak için sabırsızlanıyorum. Örneğin, yaygın kullanımda olan ve bakım baş ağrısına neden olmayacak kadar şeffaf olan bir şey mi? Boş etkinlik abonesi çağrısında kayda değer bir performans artışı var mı?

Yanıtlar:


36

Tek dezavantajı, fazladan boş delege çağırdığınız için çok hafif bir performans cezasıdır. Bunun dışında bakım cezası veya başka bir dezavantaj yoktur.


19
Aksi takdirde, olayın tam olarak bir abonesi varsa ( çok yaygın bir durum), sahte işleyici iki aboneye sahip olmasını sağlayacaktır. Tek işleyicili olaylar, iki işleyiciye göre çok daha verimli bir şekilde işlenir .
supercat

46

Performans ek yükünü tetiklemek yerine, neden her iki sorunu da hafifletmek için bir uzatma yöntemi kullanmayalım :

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

Bir kez tanımlandıktan sonra, bir daha asla boş olay kontrolü yapmanız gerekmez:

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

2
Genel bir sürüm için buraya bakın: stackoverflow.com/questions/192980/…
Benjol

1
Tam olarak yardımcı üçlü kitaplığımın yaptığı şey: thehelpertrinity.codeplex.com
Kent Boogaart

3
Bu sadece boş kontrolün iş parçacığı problemini uzantı yönteminize taşımaz mı?
PatrickV

6
Hayır. İşleyici yönteme aktarılır, bu noktada söz konusu örnek değiştirilemez.
Judah Gabriel Himango

@PatrickV Hayır, Judah haklı, handleryukarıdaki parametre yöntem için bir değer parametresidir. Yöntem çalışırken değişmeyecektir. Bunun gerçekleşmesi için bir refparametre olması gerekir (tabii ki bir parametrenin hem hem refde thisdeğiştiriciye sahip olmasına izin verilmez ) veya elbette bir alan.
Jeppe Stig Nielsen

42

Sistemler için olayların ağır faydalanmak ve performans açısından kritik olan , kesinlikle en az isteyeceksiniz düşünün yapmıyorum. Boş bir delege ile bir olayı oluşturmanın maliyeti, onu ilk önce boş bir denetimle yükseltmenin yaklaşık iki katıdır.

Makinemde kıyaslama yapan bazı rakamlar:

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

Ve işte bu rakamları almak için kullandığım kod:

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
Şaka yapıyorsun, değil mi? Çağrı 5 nanosaniye ekliyor ve siz bunu yapmaya karşı uyarıyorsunuz? Bundan daha mantıksız bir genel optimizasyon düşünemiyorum.
Brad Wilson

8
İlginç. Bulgularınıza göre, boş değeri kontrol etmek ve bir delege aramak, kontrolsüz aramaktan daha hızlıdır. Bana doğru gelmiyor. Ama yine de bu o kadar küçük bir fark ki, en uç durumlar dışında hiçbirinde fark edilebilir olduğunu düşünmüyorum.
Maurice

22
Brad, özellikle olayları yoğun şekilde kullanan performans açısından kritik sistemler için söyledim. Bu nasıl general?
Kent Boogaart

3
Boş delegeleri ararken performans cezalarından bahsediyorsunuz. Size soruyorum: birisi gerçekten etkinliğe abone olduğunda ne olur? Boş delegelerin değil abonelerin performansı konusunda endişelenmelisiniz.
Liviu Trifoi

2
Büyük performans maliyeti sıfır "gerçek" abonenin olduğu durumda değil, bir abonenin olduğu durumda gelir. Bu durumda, abonelik, abonelik iptali ve çağrı çok verimli olmalıdır çünkü çağrı listeleri ile hiçbir şey yapmak zorunda kalmazlar, ancak olayın (sistem açısından) bir yerine iki aboneye sahip olacağı gerçeği kullanımı zorlayacaktır. "sıfır" veya "bir" durumu için optimize edilmiş kod yerine "n" aboneyi işleyen kodun.
supercat

7

Bunu bir / lot / yapıyorsanız, temsilci örneklerinin hacmini azaltmak için yeniden kullandığınız tek, statik / paylaşılan boş bir temsilciye sahip olmak isteyebilirsiniz. Derleyicinin bu temsilciyi her olay için (statik bir alanda) önbelleğe aldığına dikkat edin, bu nedenle olay tanımı başına yalnızca bir temsilci örneğidir, bu nedenle bu çok büyük bir tasarruf değildir - ama belki de zahmete değer.

Her sınıftaki örnek başına alan elbette aynı alanı kaplayacaktır.

yani

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

Bunun dışında iyi görünüyor.


1
Buradaki cevaptan anlaşılıyor: stackoverflow.com/questions/703014/… derleyici optimizasyonu zaten tek bir örneğe yapıyor.
Govert

3

Muhtemelen bazı aşırı durumlar haricinde konuşulacak anlamlı bir performans cezası yoktur.

Bununla birlikte, bu numara C # 6.0'da daha az alakalı hale gelir, çünkü dil, boş olabilecek delegeleri çağırmak için alternatif bir sözdizimi sağlar:

delegateThatCouldBeNull?.Invoke(this, value);

Yukarıda, boş koşullu operatör ?., boş denetimini koşullu bir çağrı ile birleştirir.



2

Bunun biraz tehlikeli bir yapı olduğunu söyleyebilirim, çünkü sizi şöyle bir şey yapmaya teşvik ediyor:

MyEvent(this, EventArgs.Empty);

İstemci bir istisna atarsa, sunucu onunla birlikte gider.

O zaman belki de yaparsın

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

Ancak, birden fazla aboneniz varsa ve bir abone bir istisna atarsa, diğer abonelere ne olur?

Bu amaçla, null kontrolü yapan ve abone tarafından herhangi bir istisnayı yutan bazı statik yardımcı yöntemler kullanıyorum (bu, idesign'dan).

// 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
        { }
    }
}

Nitpick için değil, ama bir şey bilmiyor musun <code> if (del == null) {return; } Delege [] delegates = del.GetInvocationList (); </code> bir yarış koşulu adayı mı?
Cherian

Tam olarak değil. Temsilciler değer türleri olduğundan, delege zincirinin yalnızca UnsafeFire yöntem gövdesi tarafından erişilebilen özel bir kopyasıdır. (Uyarı: UnsafeFire satır içi hale gelirse bu başarısız olur, bu yüzden buna karşı koymak için [MethodImpl (MethodImplOptions.NoInlining)] özelliğini kullanmanız gerekir.)
Daniel Fortunov

1) Temsilciler referans türleridir 2) Değişmezler, bu nedenle bu bir yarış koşulu değildir 3) Satır içi yapmanın bu kodun davranışını değiştirdiğini düşünmüyorum. Del parametresinin satır içi olduğunda yeni bir yerel değişken olmasını beklerdim.
CodesInChaos

'Bir istisna atılırsa ne olur' çözümünüz tüm istisnaları görmezden gelmek mi? Hep ya da hemen hemen her zaman (kullanışlı kullanarak bir denemede bütün olay abonelikleri sarmak Bu yüzden Try.TryAction()işlevi değil, açık bir try{}blok), ama istisnalar göz ardı etmeyin, ben ... bunları rapor
Dave Cousineau

0

"Boş delege" yaklaşımı yerine, olay işleyicisini boşa karşı kontrol etmenin geleneksel yöntemini kapsüllemek için basit bir genişletme yöntemi tanımlanabilir. Burada ve burada anlatılmaktadır .


-2

Şimdiye kadar bu sorunun cevabı olarak bir şey gözden kaçtı: Boş değeri kontrol etmekten kaçınmak tehlikelidir .

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

Mesele şu ki: Kodunuzu kimin hangi şekilde kullanacağını asla bilemezsiniz. Bazı yıllarda kodunuzun bir hata düzeltmesi sırasında olay / işleyicinin null olarak ayarlanıp ayarlanmadığını asla bilemezsiniz.

Her zaman, eğer kontrolünü yazın.

Umarım yardımcı olur ;)

ps: Performans hesaplaması için teşekkürler.

pps: Bir olay durumundan ve geri arama örneğinden düzenledi. Geri bildirim için teşekkürler ... Örneği Visual Studio olmadan "kodladım" ve aklımdaki örneği bir olaya ayarladım. Karışıklık için özür dilerim.

ppps: Hala iş parçacığına uyup uymadığını bilmiyorum ... ama önemli bir ilke olduğunu düşünüyorum. Lütfen başka bir yığın akışı dizisini de kontrol edin


1
x.MyFunnyEvent = boş; <- Bu bile derlemez (sınıfın dışında). Bir olayın amacı yalnızca + = ve - = desteklemesidir. Ve x.MyFunnyEvent- = x.MyFunnyEvent'i sınıf dışında bile yapamazsınız, çünkü olay alıcı yarıdadır protected. Olayı yalnızca sınıfın kendisinden (veya türetilmiş bir sınıftan) ayırabilirsiniz.
CodesInChaos

haklısın ... olaylar için doğrusun ... basit bir idareci olan bir dava vardı. Afedersiniz. Düzenlemeye çalışacağım.
Thomas

Elbette, temsilciniz halka açıksa, bu tehlikeli olabilir çünkü kullanıcının ne yapacağını asla bilemezsiniz. Bununla birlikte, boş delegeleri özel bir değişken üzerinde ayarlarsanız ve + = ve - = 'yi kendiniz ele alırsanız, bu bir sorun olmayacak ve boş kontrol iş parçacığı açısından güvenli olacaktır.
ForceMagic
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.