If you were to do a null check at step 2, you would have to add a null-check to every method call.
As it is now, the vast majority of methods don't need to check that the instance isn't null. Instead they try to call the method, and if the instance is null then the attempt to get the method table to do this results in an invalid memory access, which is then trapped and turned into the NullReferenceException
by the framework. There's no more work for the running code here than there would be if the instance was known a priori to not be null.
The only time the instance has to be explicitly checked for non-nullity is when an optimisation means:
- The call was removed by inlining.
- The inlined call doesn't involve a field access (that would lead to the null reference exception anyway).
- The inlined call doesn't involve another call on the same object (ditto).
- The instance can't be shown to definitely not be null (or there'd be o worries).
In this case a field-access is added in to trigger the NullReferenceException
in the same way as a call would.
If however the rules required a null check before the arguments were evaluated, there'd need to be an explicit check added for every single call. And all it would mean in practice is that you had a NullReferenceException
thrown prior to attempting something that would have resulted in a NullReferenceException
being thrown. (They couldn't remove the logic that turns a low-address memory access violation into NullReferenceException
because it still comes up in other ways).
So the rules you suggest would in practice require a lot more work.
Related:
C# only added the rule against calling methods on null instances when it was already in internal use in the development of .NET, though not yet publicly released.
It is after all perfectly legal to call a non-virtual method on a null instance in .NET generally, by compiling to the CIL instruction call
rather than callvirt
. (For that matter, you can call a virtual method non-virtually the same way, which is how a call to base
works). This will work as long as there are no field accesses or virtual method calls on the instance (which in practice is rare, but can happen).
Prior to that, the rule had been that a null check was required only if the method was virtual.
This was done the same way as before; call the method with callvirt
and catch the memory access violation if its called on a null reference.
When the rule was changed to (unfortunately, IMO) ban any call on null objects this was done by changing the compilation to use callvirt
even when the method isn't virtual, so the memory access violation happens if the instance is null, and the resulting NullReferenceException
ensues.