85

I have seen a few mentions of this idiom (including on SO):

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

The upside is clear - it avoids the need to check for null before raising the event.

However, I am keen to understand if there are any downsides. For example, is it something that is in widespread use and is transparent enough that it won't cause a maintenance headache? Is there any appreciable performance hit of the empty event subscriber call?

Community
  • 1
  • 1
serg10
  • 31,923
  • 16
  • 73
  • 94

9 Answers9

46

Instead of inducing performance overhead, why not use an extension method to alleviate both problems:

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

Once defined, you never have to do another null event check again:

// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);
Community
  • 1
  • 1
Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
  • 2
    See here for a generic version: http://stackoverflow.com/questions/192980/boiler-plate-code-replacement-is-there-anything-bad-about-this-code – Benjol Jun 16 '09 at 07:01
  • 1
    Exactly what my helper trinity library does, incidentally: http://thehelpertrinity.codeplex.com/ – Kent Boogaart Jan 23 '12 at 19:50
  • 5
    Doesn't this just move the thread problem of the null check into your extension method? – PatrickV Apr 30 '13 at 21:21
  • 6
    No. Handler is passed into the method, at which point, that instance can't be modified. – Judah Gabriel Himango Apr 30 '13 at 21:59
  • @PatrickV No, Judah is right, the parameter `handler` above is a value parameter to the method. It won't change while the method is running. For that to occur, it would need to be a `ref` parameter (obviously it is not allowed for a parameter to have both the `ref` and the `this` modifier), or a field of course. – Jeppe Stig Nielsen Jun 21 '14 at 12:37
  • I get that @JeppeStigNielsen. But what happens if you have a registered handler, get past the null check, then it deregisters on a separate thread, then you raise it? I expect it will not break and will call the handler that just deregistered, but isn't that a violation of the contract? We shouldn't be raising events for consumers that have deregistered. – PatrickV Jun 26 '14 at 15:12
  • @PatrickV Your expectation is correct. Suppose the above `Raise` method is called and the `handler` is non-null and contains a single method on its invocation list (multicast delegate with only a single method in it). Then the null-check is OK, and the inner-most statement is to be executed. Then if some other thread unregisters from the original event, then the backing-field of the event becomes a null reference. After that, suppose the above `Raise` method resumes. The invocation will still be executed with the "old" reference to the single-method delegate. – Jeppe Stig Nielsen Jul 03 '14 at 09:32
  • @JeppeStigNielsen That's what I meant by it just moves the problem. You don't null dereference with that approach, and that is better. But you still call a method you shouldn't have. I think to respect the "-=" contract you'll have to lock in the event's remove handler and when raising. The approach in this answer probably works in most cases, but could be problematic at the edges. – PatrickV Jul 03 '14 at 19:41
43

For systems that make heavy use of events and are performance-critical, you will definitely want to at least consider not doing this. The cost for raising an event with an empty delegate is roughly twice that for raising it with a null check first.

Here are some figures running benchmarks on my 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

And here is the code I used to get these figures:

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);
            }
        }
    }
}
Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • 11
    You're kidding, right? The invocation adds 5 nanoseconds and you're warning against doing it? I can't think of a more unreasonable general optimization than that. – Brad Wilson Oct 05 '08 at 00:33
  • 9
    Interesting. According to your findings it is faster to check for null and call a delegate than just to call it without the check. Doesn't sound right to me. But anyway this is such a small difference that I don't think it is noticeable in all but the most extreme cases. – Maurice Oct 05 '08 at 07:48
  • 24
    Brad, I specifically said for performance-critical systems that make heavy use of events. How is that general? – Kent Boogaart Oct 05 '08 at 08:56
  • 3
    You're talking about performance penalties when calling empty delegates. I ask you: what happens when somebody actually subscribes to the event? You should worry about the performance of the subscribers not of the empty delegates. – Liviu Trifoi Jun 17 '11 at 08:14
  • 2
    The big performance cost doesn't come in the case where there are zero "real" subscribers, but in the case where there is one. In that case, subscription, unsubscription, and invocation should be very efficient because they won't have to do anything with invocation lists, but the fact that the event will (from the system's perspective) have two subscribers rather than one will force the use of the code that handles "n" subscribers, rather than code which is optimized for the "zero" or "one" case. – supercat May 14 '12 at 23:20
  • 3
    There is a misleading fluke in the output benchmark because two different objects are used for the warmup and the real run. Thus, the "warmup" is not effective on the second call, since it just warmed up the second object. The fluke is due to the first call to the empty delegates that has to be created, that is way longer than a simple call. It'd be nice if you could correct the results by using the same object for both call. :) – ForceMagic Feb 19 '14 at 00:28
36

The only downside is a very slight performance penalty as you are calling extra empty delegate. Other than that there is no maintenance penalty or other drawback.

Maurice
  • 27,582
  • 5
  • 49
  • 62
  • 22
    If the event would otherwise have exactly one subscriber (a *very* common case), the dummy handler will make it have two. Events with one handler are handled *much* more efficiently than those with two. – supercat May 14 '12 at 23:17
8

If you are doing it a /lot/, you might want to have a single, static/shared empty delegate that you re-use, simply to reduce the volume of delegate instances. Note that the compiler caches this delegate per event anyway (in a static field), so it is only one delegate instance per event definition, so it isn't a huge saving - but maybe worthwhile.

The per-instance field in each class will still take the same space, of course.

i.e.

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

Other than that, it seems fine.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    It seems from the answer here: http://stackoverflow.com/questions/703014/will-an-empty-delegate-eat-up-memory that the compiler does the optimization to a single instance already. – Govert Apr 04 '11 at 14:37
4

There is no meaningful performance penalty to talk about, except, possibly, for some extreme situations.

Note, however, that this trick becomes less relevant in C# 6.0, because the language provides an alternative syntax to calling delegates that may be null:

delegateThatCouldBeNull?.Invoke(this, value);

Above, null conditional operator ?. combines null checking with a conditional invocation.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • "the language provides an alternative syntax..." that hides the complexity and potential multithreading issues presented by the original language design decision (that basically requires always wrapping delegates in a null check.) I suggest that this syntax is an antipattern and was added to supports poor coding habits that have become normalized. – tekHedd Dec 22 '21 at 20:39
  • @tekHedd Going a level deeper, we should question the introduction of the null reference itself. Sir Charles Antony Richard Hoare, who invented the unfortunate thing, has called it his billion-dollar mistake. Personally, I think he is being way too modest with his estimate: today, the cost of that mistake is probably closing on a trillion-dollar mark. That's why I think that the decision to move C# toward the opt-in nullability is a step in the right direction. – Sergey Kalinichenko Dec 22 '21 at 20:54
3

It is my understanding that the empty delegate is thread safe, whereas the null check is not.

Christopher Bennage
  • 2,578
  • 2
  • 22
  • 32
  • 2
    Neither of the patterns make the event thread safe as stated in the answer here: http://stackoverflow.com/questions/10282043/is-this-a-better-way-to-fire-invoke-events-without-a-null-check-in-c – Beachwalker Jul 25 '13 at 09:08
  • @Beachwalker That answer is misleading. Delegate calls are safe, as long as the delegate is not null. https://stackoverflow.com/a/6349163/2073670 – tekHedd Dec 22 '21 at 20:44
2

I would say it's a bit of a dangerous construct, because it tempts you to do something like :

MyEvent(this, EventArgs.Empty);

If the client throws an exception, the server goes with it.

So then, maybe you do:

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

But, if you have multiple subscribers and one subscriber throws an exception, what happens to the other subscribers?

To that end, I've been using some static helper methods that do the null check and swallows any exception from the subscriber side (this is from 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
        { }
    }
}
Scott P
  • 3,775
  • 1
  • 24
  • 30
  • Not to nitpick, but dont you thing if (del == null) { return; } Delegate[] delegates = del.GetInvocationList(); is a race condition candidate? – Cherian Nov 12 '08 at 12:30
  • Not quite. Since delegates are value types, del is actually a private copy of the delegate chain which is accessible only to the UnsafeFire method body. (Caveat: This fails if UnsafeFire gets inlined, so you'd need to use the [MethodImpl(MethodImplOptions.NoInlining)] attribute to inure against it.) – Daniel Fortunov Mar 12 '09 at 21:29
  • 1) Delegates are reference types 2) They are immutable, so it's not a race condition 3) I don't think inlining does change the behavior of this code. I'd expect that the parameter del becomes a new local variable when inlined. – CodesInChaos Oct 19 '10 at 09:55
  • Your solution to 'what happens if an exception is thrown' is to ignore all of the exceptions? This is why I always or almost always wrap all event subscriptions in a try (using a handy `Try.TryAction()` function, not an explicit `try{}` block), but I don't ignore the exceptions, I report them... – Dave Cousineau Sep 12 '16 at 00:37
0

Instead of "empty delegate" approach one can define a simple extension method to encapsulate the conventional method of checking event handler against null. It is described here and here.

Community
  • 1
  • 1
vkelman
  • 1,501
  • 1
  • 15
  • 25
-2

One thing is missed out as an answer for this question so far: It is dangerous to avoid the check for the null value.

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

The thing is: You never know who will use your code in which way. You never know, if in some years during a bug fix of your code the event/handler is set to null.

Always, write the if check.

Hope that helps ;)

ps: Thanks for the performance calculation.

pps: Edited it from a event case to and callback example. Thanks for the feedback ... I "coded" the example w/o Visual Studio and adjusted the example I had in mind to an event. Sorry for the confusion.

ppps: Do not know if it still fits to the thread ... but I think it is an important principle. Please also check another thread of stackflow

Community
  • 1
  • 1
Thomas
  • 42
  • 1
  • 3
  • 1
    x.MyFunnyEvent = null; <- That doesn't even compile(outside of the class). The point of an event is only supporting += and -=. And you can't even do x.MyFunnyEvent-=x.MyFunnyEvent outside of the class since the event getter is quasi `protected`. You can only break the event from the class itself(or from a derived class). – CodesInChaos Oct 23 '10 at 09:25
  • you are right ... true for events ... had a case with a simple handler. Sorry. I will try to edit. – Thomas Oct 24 '10 at 10:12
  • Of course if your delegate is public that would be dangerous because you never know what the user will do. However, if you set the empty delegates on a private variable and you handle the += and -= yourself, that won't be a problem and the null check will be thread safe. – ForceMagic Dec 11 '13 at 22:01