Czy jest jakiś minus dodawania anonimowego pustego delegata w deklaracji zdarzenia?

Widziałem kilka wzmianek o tym idiomie (w tym Na SO):

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

Plusa jest jasna-pozwala uniknąć konieczności sprawdzania null przed podniesieniem zdarzenia.

Jestem jednak ciekaw, czy są jakieś wady. na przykład, czy jest to coś, co jest w powszechnym użyciu i jest wystarczająco przejrzyste, że nie spowoduje bólu głowy w utrzymaniu? Czy jest jakieś zauważalne trafienie wydajności pustego połączenia abonenckiego zdarzenia?

Author: Community, 2008-10-04

9 answers

Jedynym minusem jest bardzo niewielka kara za wydajność, ponieważ nazywasz dodatkowego pustego delegata. Poza tym nie ma kary alimentacyjnej lub innych wad.

 36
Author: Maurice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-04 20:59:31

Zamiast wywoływać narzuty wydajności, dlaczego nie użyć metody rozszerzenia , Aby złagodzić oba problemy:

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

Po zdefiniowaniu nigdy nie musisz ponownie sprawdzać żadnego zdarzenia null:

// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);
 46
Author: Judah Himango,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:24

W przypadku systemów, które intensywnie wykorzystują zdarzenia i mają kluczowe znaczenie dla wydajności , na pewno będziesz chciał przynajmniej rozważyć nie robić tego. Koszt podniesienia zdarzenia z pustym delegatem jest mniej więcej dwa razy większy niż koszt podniesienia zdarzenia z zerowym czekiem.

Oto kilka liczb uruchamiających benchmarki na mojej maszynie:

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

A oto kod, którego użyłem do uzyskania tych liczb:

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);
            }
        }
    }
}
 41
Author: Kent Boogaart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-11 23:12:01

Jeśli robisz to z /lot/, możesz chcieć mieć pojedynczy, statyczny / współdzielony pusty delegat, którego ponownie używasz, po prostu w celu zmniejszenia objętości wystąpień delegatów. Zauważ, że kompilator i tak buforuje tego delegata na zdarzenie (w polu statycznym), więc jest to tylko jedna instancja delegata na definicję zdarzenia, więc nie jest to zapis ogromny - ale może warto.

Pole per-instance w każdej klasie będzie oczywiście zajmowało tę samą przestrzeń.

Tzn.

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

Inne niż to, wydaje się w porządku.

 7
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-04 22:15:58

Rozumiem, że pusty delegat jest bezpieczny dla wątku, podczas gdy sprawdzanie null nie jest.

 2
Author: Christopher Bennage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-05 04:55:44

Powiedziałbym, że jest to trochę niebezpieczna konstrukcja, ponieważ kusi cię do zrobienia czegoś takiego jak:

MyEvent(this, EventArgs.Empty);

Jeśli klient wyrzuci wyjątek, serwer pójdzie z nim.

Więc może tak:

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

Ale jeśli masz wielu subskrybentów i jeden subskrybent rzuca wyjątek, co się stanie z innymi subskrybentami?

W tym celu używam statycznych metod pomocniczych, które sprawdzają null i połykają każdy wyjątek od strony Abonenta (to jest z idesign).

// 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
        { }
    }
}
 2
Author: Scott P,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-05 21:47:43

Nie ma znaczącej kary za wydajność, o której można mówić, z wyjątkiem, być może, w niektórych ekstremalnych sytuacjach.

Zauważ jednak, że ta sztuczka staje się mniej istotna w C# 6.0, ponieważ Język zapewnia alternatywną składnię do wywoływania delegatów, które mogą być null:

delegateThatCouldBeNull?.Invoke(this, value);

Powyżej, operator warunkowy null ?. łączy sprawdzanie null z wywołaniem warunkowym.

 2
Author: dasblinkenlight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-10 18:23:23

Zamiast podejścia "empty delegate" można zdefiniować prostą metodę rozszerzenia, która enkapsuluje konwencjonalną metodę sprawdzania obsługi zdarzeń pod kątem null. Jest on opisany tutaj i Tutaj.

 0
Author: vkelman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:18

Jedna rzecz została pominięta jako odpowiedź na to pytanie: niebezpieczne jest unikanie sprawdzania wartości null .

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
Rzecz w tym, że nigdy nie wiadomo, kto użyje Twojego kodu w jaki sposób. Nigdy nie wiadomo, czy za kilka lat podczas naprawy Twojego kodu event/handler zostanie ustawiony na null.

Zawsze należy wpisać "if check".

Mam nadzieję, że to pomoże;)

Ps: dzięki za obliczenia wydajności.

Pps: edytował go z przypadku zdarzenia do i przykład wywołania zwrotnego. Dzięki za opinie... "Zakodowałem" przykład w / O Visual Studio i dostosowałem przykład, który miałem na myśli do zdarzenia. Przepraszam za zamieszanie.

Ppps: Nie wiem, czy nadal pasuje do wątku ... ale myślę, że to ważna zasada. Sprawdź również kolejny wątek stackflow

 -1
Author: Thomas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02