82 votes

Y a-t-il un inconvénient à ajouter un délégué anonyme vide lors de la déclaration d'un événement ?

J'ai vu quelques mentions de cet idiome (notamment sur le SO ) :

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

L'avantage est clair : il n'est pas nécessaire de vérifier si l'événement est nul avant de le déclencher.

Toutefois, j'aimerais savoir s'il y a des inconvénients. Par exemple, s'agit-il d'un produit largement utilisé et suffisamment transparent pour ne pas causer de problème de maintenance ? L'appel à l'abonné d'un événement vide a-t-il une incidence notable sur les performances ?

46voto

Judah Himango Points 27365

Au lieu d'induire un surcoût de performance, pourquoi ne pas utiliser une méthode d'extension pour atténuer les deux problèmes :

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

Une fois défini, vous ne devez plus jamais effectuer de contrôle d'événement nul :

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

42voto

Kent Boogaart Points 97432

Pour les systèmes qui font un usage intensif des événements et sont critiques en termes de performances vous voudrez certainement au moins envisager ne pas le faire. Le coût pour déclencher un événement avec un délégué vide est environ deux fois plus élevé que pour le déclencher avec une vérification nulle en premier lieu.

Voici quelques chiffres en faisant des benchmarks sur ma machine :

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

Et voici le code que j'ai utilisé pour obtenir ces chiffres :

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);
            }
        }
    }
}

36voto

Maurice Points 22343

Le seul inconvénient est une très légère pénalité de performance car vous appelez un délégué vide supplémentaire. En dehors de cela, il n'y a pas de pénalité de maintenance ou d'autre inconvénient.

7voto

Marc Gravell Points 482669

Si vous le faites beaucoup, vous pouvez avoir un délégué unique, statique/partagé et vide que vous réutilisez, simplement pour réduire le volume des instances de délégués. Notez que le compilateur met en cache ce délégué par événement de toute façon (dans un champ statique), donc il n'y a qu'une seule instance de délégué par définition d'événement, donc ce n'est pas un problème d'utilisation. énorme d'économie - mais qui en vaut peut-être la peine.

Le champ par instance de chaque classe occupera toujours le même espace, bien entendu.

c'est-à-dire

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

A part ça, ça semble bien.

2voto

D'après ce que j'ai compris, le délégué vide est thread safe, alors que la vérification de nullité ne l'est pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X