Recently I came across some strange behaviour of my application. It has been developed mainly in C# but CLI/C++ was also used to achieve better performance. I was getting a System.NullReferenceException in a very simple method at the TimeSpan comparison:
TimeSpan _timestamp; void UpdateFrame(TimeSpan timestamp) { if(TimeSpan::Equals(_timestamp, timestamp) == false)
It was obvious that the only reference used in this expression was implicit this (this._timestamp). I added an assert statement and it turned out that this is actually null. After short investigation I managed to prepared short program presenting this phenomenon. It is C++/CLI.
using namespace System; using namespace System::Reflection; public class Unmanaged { public: int value; }; public ref class Managed { public: int value; Unmanaged* GetUnmanaged() { SampleMethod(); return new Unmanaged(); } void SampleMethod() { System::Diagnostics::Debug::Assert(this != nullptr); this->value = 0; } }; public ref class ManagedAccessor { public: property Managed^ m; }; int main(array<System::String ^> ^args) { ManagedAccessor^ ma = gcnew ManagedAccessor(); // Confirm that ma->m == null System::Diagnostics::Debug::Assert(ma->m == nullptr); // Invoke method on the null reference delete ma->m->GetUnmanaged(); return 0; }
Does anybody know how can it be possible? Is it a bug in the compiler?
tl;dr, "this" can only be called from a non-static method and we all know that a non-static method is called from some sort of object which cannot be null.
To check for null variables, you can use a strict equality operator ( === ) to compare the variable with null . This is demonstrated below, where the boolean expression evaluates to true for only for null and evaluates to false for other falsy values.
If you think of it from a programming (i.e. pointer reference) point of view then, yes, two references of null have the same pointer value and, since most of the popular languages will fall back to pointer-equality if no custom equality is defined, null does equal null.
null can only be assigned to reference type, you cannot assign null to primitive variables e.g. int, double, float, or boolean. The compiler will complain if you do so, as shown below.
In C++ (and presumably in C++/CLI) there's nothing preventing you from trying to call methods on a NULL pointer. In most implementations, a virtual method call will crash at the point of the call because the runtime won't be able to read the virtual method table. However, a non-virtual method call is just a function call with some parameters, one of which is the this
pointer. If it's null, then that is what is passed to the function.
I believe the result of calling any member function on a NULL
(or nullptr
) pointer is officially "undefined behaviour".
Thank you Greg for your answer it happens the way you describe it. However, I am not pleased with this situation because it means that I have to place
if(this == nullptr) throw gcnew ArgumentException("this");
at the beginning of every method. Only this would guarantee that my method won't appear at the top of stack-trace as a faulty piece of code without argument validation.
I have never come across (this == null) when I was writing in C#. Therefore, I decided to find out how is it different from C++/CLI. I created a sample application in C++/CLI:
namespace ThisEqualsNull{ public ref class A { public: void SampleMethod() { System::Diagnostics::Debug::Assert(this != nullptr); } }; public ref class Program{ public: static void Main(array<System::String ^> ^args) { A^ a = nullptr; a->SampleMethod(); } }; }
And a small program in C# that uses the C++/CLI classes with the same Main method:
class Program { static void Main(string[] args) { A a = null; a.SampleMethod(); } }
Then I disassembled them with Red Gate's .NET Reflector:
C++/CLI .method public hidebysig static void Main(string[] args) cil managed { .maxstack 1 .locals ( [0] class ThisEqualsNull.A a) L_0000: ldnull L_0001: stloc.0 L_0002: ldnull L_0003: stloc.0 L_0004: ldloc.0 L_0005: call instance void ThisEqualsNull.A::SampleMethod() L_000a: ret } C# .method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 1 .locals init ( [0] class [ThisEqualsNull]ThisEqualsNull.A a) L_0000: nop L_0001: ldnull L_0002: stloc.0 L_0003: ldloc.0 L_0004: callvirt instance void [ThisEqualsNull]ThisEqualsNull.A::SampleMethod() L_0009: nop L_000a: ret }
The important parts are:
C++/CLI L_0005: call instance void ThisEqualsNull.A::SampleMethod() C# L_0004: callvirt instance void [ThisEqualsNull]ThisEqualsNull.A::SampleMethod()
Where:
And now the final conclusion:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With