NullReferenceException
:
public class Tester { public string Property { get; set; } public void Foo() { this.Property = "Some string"; // NullReferenceException } }
NullReferenceException
. What business, I think - really rantaym stopped checking the presence of an instance before calling instance methods?
Tester
class there is an instance method Foo
and an instance property Property
. Someone called the Foo
method, but a this.Property
found on the call to this.Property
, which led to the generation of the NullReferenceException exception.
this == null
in the string, and therefore the string this.Property = smth
cannot access the property. But for a C # programmer, this sounds completely impossible - after all, if the Foo
method was somehow called, the class instance exists and this
cannot be null
! How could you call a method on null
?
static class Program { static void Main() { Tester t = null; t.Foo(); } }
NullReferenceException
on the string t.Foo();
, but does not enter the Foo
method. It that turns out, under any conditions rantaym forgot to execute check on null
?
null
), but the C ++ / CLI compiler, with which the code was compiled, which in the original way called the Foo
method. Yes, the participation of C ++ / CLI in this story would immediately have caused a lot of suspicion, and I didn’t say anything special about it at first, so that it was more interesting :)
Tester
class):
int main() { Tester ^t = nullptr; t->Foo(); }
NullReferenceException
inside the Foo
method, just like in the original case. That is, the instance method Foo
somehow called from the zero reference, bypassing any checks.
ildasm
and parse the program code in C #. I give a full listing of the Program.Main
method (in the comments I gave the source code lines corresponding to the bytecode):
.method private hidebysig static void Main() cil managed { .entrypoint // Code size 11 (0xb) .maxstack 1 .locals init ([0] class [Shared]ThisIsNull.Tester t) IL_0000: nop IL_0001: ldnull IL_0002: stloc.0 // Tester t = null; IL_0003: ldloc.0 IL_0004: callvirt instance void [Shared]ThisIsNull.Tester::Foo() // t.Foo() IL_0009: nop IL_000a: ret }
IL_0004
. We see that the compiler has called the Foo
method using the callvirt
. Now compare with the corresponding code in C ++ / CLI:
.method assembly static int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) main() cil managed { .vtentry 1 : 1 // Code size 12 (0xc) .maxstack 1 .locals ([0] class [Shared]ThisIsNull.Tester t) IL_0000: ldnull IL_0001: stloc.0 // Tester ^t = nullptr; IL_0002: ldnull IL_0003: stloc.0 // t = nullptr; IL_0004: ldloc.0 IL_0005: call instance void [Shared]ThisIsNull.Tester::Foo() // t->Foo(); IL_000a: ldc.i4.0 IL_000b: ret }
callvirt
, but via call
.
callvirt
is actually intended for virtual calls. However, it has another small feature - since virtual calls are usually made to the CLI via a virtual method table, it is the responsibility of the callvirt
also check the null
reference and throw a NullReferenceException
exception if something went wrong.
call
instruction simply calls the method without checking the links (and not using the virtual dispatch mechanisms).
callvirt
instruction feature and therefore generates it for all calls in general (except for static and explicit calls to the base class methods through the base.
) - just because it protects the code from the method call of the zero reference. At the same time, the C ++ / CLI compiler operates according to the good old laws of the System.String
, which once caused questions on StackOverflow , is also interesting:
public bool Equals(String value) { if (this == null) //this is necessary to guard against reverse-pinvokes and throw new NullReferenceException(); //other callers who do not use the callvirt instruction if (value == null) return false; if (Object.ReferenceEquals(this, value)) return true; return EqualsHelper(this, value); }
null
in this way. The fact is that the comparison of strings in the EqualsHelper
method EqualsHelper
implemented using an unsafe
code, which may well try to access the memory section at the zero address, which will certainly lead to all sorts of bad consequences.
((string)null).Equals(null)
call could return false
, rather than fall from a NullReferenceException
, as it should.
this != null
even when invoking instance methods and properties.this == null
for various reasons (code generation, reflection, compilers of other languages), and you need to be ready for this. If you are developing a library intended for widespread use in an interop environment, you may even need to add null
checks to public methods of externally accessible classes.Source: https://habr.com/ru/post/252249/