5

When calling a delegate you always have to check if it is not null. This is an often cause for errors. Since delegates are more or less just a list of functions, I would assume, that this could have been easily checked by the delegate itself.

Does anybody know, why it has been implemented as it is?

martin
  • 53
  • 1
  • 4
  • 6
    If a delegate is null how would you use it to check itself? It's like knocking on your son's room door and asking if he's in there, and expecting a "No" from him if he isn't. – BoltClock Oct 19 '10 at 07:51
  • It's not just a list of functions but rather a list of deferrable method calls (including parameters and the object the method is associated to). – vc 74 Oct 19 '10 at 07:56
  • See [this question](http://stackoverflow.com/questions/170907/is-there-a-downside-to-adding-an-anonymous-empty-delegate-on-event-declaration) for a way to make the null checks unnecessary (at the cost of extra CPU cycles). – Paul Ruane Oct 19 '10 at 08:16

7 Answers7

7

This may be stating the obvious, but you can declare your event to point to a no-op handler and then you don't need to check for null when invoking.

public event EventHandler MyEvent = delegate { };

Then you can call the handlers pointed to by MyEvent without checking for null.

Brian Rasmussen
  • 114,645
  • 34
  • 221
  • 317
4

The delegate itself can't check it. Since it is null you can't call methods on it.

The C# compiler on the other hand could automatically insert the null-check. But I don't know what behavior to use if the delegate is a function with a return-value.

One might argue that the C# compiler should insert that check for void-functions to avoid boilerplate code in that common case. But that decision is now in the past, you can only fix it without breaking existing programs if you get a time-machine.

You can use extension methods(since they can be called on a null object) to automate the null-check for events:
http://blogs.microsoft.co.il/blogs/shayf/archive/2009/05/25/a-handy-extension-method-raise-events-safely.aspx
And since a parameter is a temp variable you don't need to manually assign the event to a temp variable for thread-safety.

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • Well, what behaviour would you use for a multicast delegate with a return value? – Eamon Nerbonne Oct 19 '10 at 07:56
  • ...the point being that delegates are both *functions* where multicast and nullability just don't make sense, and events, where both could be perfectly normal and no reason to throw. – Eamon Nerbonne Oct 19 '10 at 07:58
  • AFAIK .net just returns the value of the last delegate. Personally I'm not sure if I would have allowed multicast delegates of non void functions. – CodesInChaos Oct 19 '10 at 07:58
  • Yeah; I agree - it's an anachronism related to their original design intent as events, and less appropriate for modern functional style code. – Eamon Nerbonne Oct 19 '10 at 08:12
  • Delegates are not events. They are the encapsulation of methods which you intend to be called in response to an event being raised. A multi-cast delegate is simply a list of multiple of these methods. If you have a multicast delegate with methods that return values, you should not call the thing all at once, but you should call each delegate instance alone, capture the result and handle any exceptions, then keep calling the rest. and yes - you need to check to assure each instance is not `null`, first. – Andrew Barber Oct 19 '10 at 08:17
  • @Andrew barber: you're missing the forest for the trees. The question was **why** delegates work as they do, not how they do so. One *key* use-case for delegates is clearly events. In that sense, delegates *are* events and the implementation details enabling that are just that - mere details. It is *because* they are events that things like transparent multicast delegate invocation make sense *in the first place*. For *that* use-case, ignoring calls to `null` references makes perfect sense - even though ignoring null and being multicast don't make sense for functional programming. – Eamon Nerbonne Oct 19 '10 at 10:44
  • @Eamon You are missing the trees for the weeds. *Why* is not a valid question, because a Delegate object reference *does not exist* when it is `null`. That's like asking, "Why didn't that police officer stop the purse snatcher", when there was no police officer there in the first place. You argue about what the police officer should have done all you want - the fact is, there wasn't one. – Andrew Barber Oct 19 '10 at 10:46
  • Of course why is a valid question. What are the semantics of a null delegate event? Well, actually, they *almost always* represent *take no action* - as witnessed by such `if(myEvent!=null)myEvent();` calls. Given that that is the semantics in the usual case *for events*, it is reasonable to ask why the language simply doesn't *define* a call to a null-valued delegate reference to be equivalent to the empty statement. – Eamon Nerbonne Oct 19 '10 at 10:54
  • To the extent you speak of, I fully and completely answered "why", as did others with the same answer: There is no delegate. That **is** why. You're going off on numerous tangents here. – Andrew Barber Oct 19 '10 at 10:58
  • Even if the delegate is null the C# compiler still could implicitly insert that null-check. And the question why it doesn't insert it is a valid one. – CodesInChaos Oct 19 '10 at 12:21
  • @Andrew: Even if a compiler wouldn't want to perform such a check-and-skip when attempting to declare something declared as a delegate, it seems rather piggish of the C# designers not to do so when the thing in question is DECLARED as an event. – supercat Jan 04 '11 at 19:03
3

Internally, the compiler will generate 2 methods add_MyEvent and remove_MyEvent for each event declared in a class. When you write, MyEvent += ..., the compiler will actually generate a call to add_MyEvent which in turn will call System.Delegate.Combine.

Combine takes 2 delegates in parameter and creates a new (multicast) delegate from the 2 original delegates, handling the case when one of them is null (which is the case the first time you call +=).

I guess the compiler could have been a bit smarter and also handled the event call so that when you call MyEvent(), it would actually generate a null check and actually invoke the delegate only if not null. (It would be nice to have Eric Lippert's view on that one).

vc 74
  • 37,131
  • 7
  • 73
  • 89
  • I always prefer for such checks to be left to me, personally. Too many 'little checks' being done for us by the framework, and we are back in the old VB6 world. +1 for the detailed explanation on the multicast delegates being created. – Andrew Barber Oct 19 '10 at 08:51
  • I understand your point Andrew but can you think of any case where you wouldn't want to check the nullity of an event? – vc 74 Oct 19 '10 at 08:53
  • Absolutely; in a case where I am so confident that a Delegate instance has been assigned to the variable that it is better to simply catch the `NullReferenceException` that I know isn't going to happen anyway. For instance, if all the code involved is self-contained. – Andrew Barber Oct 19 '10 at 10:53
2

The reason is performance (actually, that's my best guess). Events and delegates are made with a lot of compiler magic, stuff that cannot be replicated by mere C# code. But underneath it goes something like this:

A delegate is a compiler-generated class that inherits from MulticastDelegate class, which itself derives from the Delegate class. Note these two classes are magic and you can't inherit from them yourself (well, maybe you can, but you won't be able to use them very well).

An event however is implemented something like this:

private MyEventDelegateClass __cgbe_MyEvent; // Compiler generated backing field

public event MyEventDelegateClass MyEvent
{
    add
    {
        this.__cgbe_MyEvent = (MyEventDelegateClass)Delegate.Combine(this.__cgbe_MyEvent, value);
    }
    remove
    {
        this.__cgbe_MyEvent = (MyEventDelegateClass)Delegate.Remove(this.__cgbe_MyEvent, value);
    }
    get
    {
        return this.__cgbe_MyEvent;
    }
}

OK, so this isn't real code (nor is it exactly the way it is in real life), but it should give you an idea of what's going on.

The point is that the backing field initially really is null. That way there is no overhead for creating an instance of MyEventDelegateClass when creating your object. And that's why you have to check for null before invoking. Later on, when handlers get added/removed, an instance of MyEventDelegateClass is created and assigned to the field. And when the last handler is removed, this instance is also lost and the backing field is reset to null again.

This is the principle of "you don't pay for what you don't use". As long as you don't use the event, there will be no overhead for it. No extra memory, no extra CPU cycles.

Vilx-
  • 104,512
  • 87
  • 279
  • 422
  • Right, but for a typical void-returning event, a null backing field thus represents the empty list of event handlers - and calling this is semantically a no-op. So it's reasonable to use an (efficient) sentinel value for the empty list but nevertheless not throw when calling it. – Eamon Nerbonne Oct 19 '10 at 08:15
  • @Eamon Nerbonne - I can only guess what the .NET team thought when they built this. My first thought is that they didn't want to make the distinction between a void-returning event and a non-void-returning event. That way the compiler is simpler and the code is more uniform (and the programmer needs to know less). – Vilx- Oct 19 '10 at 08:49
1

If a delegate instance is null, how can it "check itself"? It's null... that means it does not exist, basically. There is nothing there to check itself in the first place. That's why your code which attempts to call the delegate must do that checking, first.

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
  • Hm, I don't know the internal of the delegate. Actually you just make a – martin Oct 19 '10 at 08:46
  • The 'internal' of the delegate *do not matter at all*. A non-existent object can not do anything, period. – Andrew Barber Oct 19 '10 at 08:48
  • The delegate internals indeed do not matter. What matters is the language specification in the face of such a situation. And, by extension, if you wish various .NET languages to deal with the same situation identically (which is not required), what the IL specification says should happen. There is no requirement for either to throw an exception; the specification *chooses* to do so for a *reason*. – Eamon Nerbonne Oct 19 '10 at 10:57
  • 1
    @AndrewBarber: If `foo` is the name of an *"standard" event*, the compiler already recognizes that a statement like `foo += whatever` is not a request to compute `foo+whatever` and store the result back to `foo`, but is instead a request to perform an atomic-event-subscribe operation. There's no reason the language had to regard `foo(sender,args)` as an unconditional attempt to invoke the backing delegate, rather than as a request to invoke the event if any subscribers exist. – supercat Dec 13 '12 at 19:58
  • @AndrewBarber: While it's obviously too late to retroactively change C#, I would posit that the language would have been better if it were possible to do everything necessary without needing direct access to the delegate (which should in turn be given a name different from the event name). – supercat Dec 13 '12 at 19:59
1

I'd guess the reasons are history and consistency.

It is consistent with the way other types are handled; e.g. there's no language or platform assistence in dealing with nulls - it's the programmers responsibility to ensure the null placeholder is never actually used - there's no means of only optionally calling a method if the object reference you wish to call it on is non-null either, after all.

It's history, since null references happen to be included by default in most types, even though this isn't necessary. That is, rather than treating reference types like value types and require an additional "nullability" annotation to permit nullability, reference types are always nullable. I'm sure there were reasons for this back in the day when Java and .NET were designed, but it introduces many unnecessary bugs and complexities which are easy to avoid in a strongly typed language (e.g., .NET value types). But given the historical inclusion of null as a type-system wide "invalid" value, so to speak, it's only natural to do so for delegates as well.

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
1

"Since delegates are more or less just a list of functions, I would assume, that this could have been easily checked by the delegate itself".

Your assumption is wrong. Plain and simple.

explorer
  • 11,710
  • 5
  • 32
  • 39