tags:

views:

149

answers:

7

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?

+2  A: 

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
Hm, I don't know the internal of the delegate. Actually you just make a
martin
The 'internal' of the delegate *do not matter at all*. A non-existent object can not do anything, period.
Andrew Barber
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
And my answer addressed that much less wordily than your tangents have: There is no object. It's a `null` reference. That's exactly what `NullReferenceException` is for. Wow, crazy how that works out so nice and neat, eh?
Andrew Barber
+3  A: 

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.

CodeInChaos
Well, what behaviour would you use for a multicast delegate with a return value?
Eamon Nerbonne
...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
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.
CodeInChaos
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
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
@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
@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
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
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
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.
CodeInChaos
+1  A: 

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-
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
@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-
A: 

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
+4  A: 

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
I don't like to do that because some other code might set the event back to null. I prefer to null-check.
Iain Galloway
@Iain: The code would have to be in the declaring type as the event cannot be set to null from the outside, so that does limit the potential for errors here, but you're right that it could happen.
Brian Rasmussen
@Brian: Aye. The probability of it actually happening in practise is pretty slim.
Iain Galloway
This my prefered solution as well. Simple; consise; and it keeps down the usage complexity nicely as well.
Eamon Nerbonne
+1  A: 

"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.

mumtaz
+2  A: 

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
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
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
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