Reputation: 15901
I've always thought that it's impossible for this
to be null inside instance method body. Following simple program demonstrates that it is possible. Is this some documented behaviour?
class Foo
{
public void Bar()
{
Debug.Assert(this == null);
}
}
public static void Test()
{
var action = (Action)Delegate.CreateDelegate(typeof (Action), null, typeof(Foo).GetMethod("Bar"));
action();
}
UPDATE
I agree with the answers saying that it's how this method is documented. However, I don't really understand this behaviour. Especially because it's not how C# is designed.
We had gotten a report from somebody (likely one of the .NET groups using C# (thought it wasn't yet named C# at that time)) who had written code that called a method on a null pointer, but they didn’t get an exception because the method didn’t access any fields (ie “this” was null, but nothing in the method used it). That method then called another method which did use the this point and threw an exception, and a bit of head-scratching ensued. After they figured it out, they sent us a note about it. We thought that being able to call a method on a null instance was a bit weird. Peter Golde did some testing to see what the perf impact was of always using callvirt, and it was small enough that we decided to make the change.
http://blogs.msdn.com/b/ericgu/archive/2008/07/02/why-does-c-always-use-callvirt.aspx
Upvotes: 36
Views: 6416
Reputation: 667
this is a readonly reference in C# classes. Accordingly and as expected this can be used like any other references (in read only mode) ...
this == null // readonly - possible
this = new this() // write - not possible
Upvotes: 0
Reputation: 13529
this
is a reference, so there is no problem with its being null
from the perspective of the type system.
You may ask why NullReferenceException
was not thrown. The full list of circumstances when CLR throws that exception is documented. Your case is not listed. Yes, it is a callvirt
, but to Delegate.Invoke
(see here) rather than to Bar
, and so the this
reference is actually your non-null delegate!
The behavior you see has an interesting implementational consequence for CLR. A delegate has a Target
property (corresponds to your this
reference) that is quite frequently null
, namely when the delegate is static (imagine Bar
be static). Now there is, naturally, a private backing field for the property, called _target
. Does _target
contain a null for a static delegate? No it doesn't. It contains a reference to the delegate itself. Why not null? Because a null is a legitimate target of a delegate as your example shows and CLR does not have two flavors of a null
pointer to distinguish the static delegate somehow.
This bit of trivium demonstrates that with delegates, null targets of instance methods are no afterthought. You may still be asking the ultimate question: but why they had to be supported?
The early CLR had an ambitious plan of becoming, among others, the platform of choice even for sworn C++ developers, a goal that was approached first with Managed C++ and then with C++/CLI. Some too challenging language features were omitten, but there was nothing really challenging about supporting instance methods executing without an instance, which is perfectly normal in C++. Including delegate support.
The ultimate answer therefore is: because C# and CLR are two different worlds.
More good reading and even better reading to show the design allowing null instances shows its traces even in very natural C# syntactic contexts.
Upvotes: 5
Reputation: 7010
Try the documentation for Delegate.CreateDelegate()
at msdn.
You're "manually" calling everything, and thus instead of passing an instance in for the this
pointer, you're passing null. So it can happen, but you have to try really really hard.
Upvotes: 5
Reputation: 10610
Because you're passing null
into the firstArgument
of Delegate.CreateDelegate
So you're calling an instance method on a null object.
http://msdn.microsoft.com/en-us/library/74x8f551.aspx
If firstArgument is a null reference and method is an instance method, the result depends on the signatures of the delegate type type and of method:
If the signature of type explicitly includes the hidden first parameter of method, the delegate is said to represent an open instance method. When the delegate is invoked, the first argument in the argument list is passed to the hidden instance parameter of method.
If the signatures of method and type match (that is, all parameter types are compatible), then the delegate is said to be closed over a null reference. Invoking the delegate is like calling an instance method on a null instance, which is not a particularly useful thing to do.
Upvotes: 23
Reputation: 13545
Sure you can call into a method if you are using the call IL instruction or the delegate approach. You will set this booby trap only off if you try to access member fields which will give you the NullReferenceException you did seek for.
try
int x;
public void Bar()
{
x = 1; // NullRefException
Debug.Assert(this == null);
}
The BCL does even contain explicit this == null checks to aid debugging for languages which do not use callvirt (like C#) all the time. See this question for further infos.
The String class for example has such checks. There is nothing mysterious about them except that you will not see the need for them in languages like C#.
// Determines whether two strings match.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(Object obj)
{
//this is necessary to guard against reverse-pinvokes and
//other callers who do not use the callvirt instruction
if (this == null)
throw new NullReferenceException();
String str = obj as String;
if (str == null)
return false;
if (Object.ReferenceEquals(this, obj))
return true;
return EqualsHelper(this, str);
}
Upvotes: 12