Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can "this" be null in C# virtual methods? What happens with the rest of instance methods?

Tags:

c#

this

c#-4.0

il

I was curious if there is a way for this to be null in a virtual method in C#. I assume it is not possible. I saw that in existing code, during a code review and I would like to be 100% sure to comment for its removal, but I would like some confirmation and some more context from the community. Is it the case that this != null in any non-static / instance method? Otherwise it would have been a null pointer exception right? I was thinking of extension methods and such or any C# feature that I could possibly be not familiar with coming from years of Java.

like image 305
Michail Michailidis Avatar asked Jul 31 '15 13:07

Michail Michailidis


People also ask

Can this ever be NULL?

According to the Standard, this cannot be null and therefore the checks and the corresponding code branches can be eliminated, which will greatly affect the code dependent on the comparison of "this" pointer to null.

What can be NULL in C?

Null is a built-in constant that has a value of zero. It is the same as the character 0 used to terminate strings in C. Null can also be the value of a pointer, which is the same as zero unless the CPU supports a special bit pattern for a null pointer.

Is NULL valid in C?

In C or C++, there is no special method for comparing NULL values. We can use if statements to check whether a variable is null or not.

When can this pointer be NULL?

A null pointer is a pointer which points nothing. Some uses of the null pointer are: a) To initialize a pointer variable when that pointer variable isn't assigned any valid memory address yet. b) To pass a null pointer to a function argument when we don't want to pass any valid memory address.


2 Answers

It's not standard C# but, further to the answers from Lasse and Jon, with a bit of IL-fiddling you can make a non-virtual call (to either virtual or non-virtual methods) passing a null this:

using System;
using System.Reflection.Emit;

class Test
{
    static void Main()
    {
        CallWithNullThis("Foo");
        CallWithNullThis("Bar");
    }

    static void CallWithNullThis(string methodName)
    {
        var mi = typeof(Test).GetMethod(methodName);

        // make Test the owner type to avoid VerificationException
        var dm = new DynamicMethod("$", typeof(void), Type.EmptyTypes, typeof(Test));
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldnull);
        il.Emit(OpCodes.Call, mi);
        il.Emit(OpCodes.Ret);

        var action = (Action)dm.CreateDelegate(typeof(Action));
        action();
    }

    public void Foo()
    {
        Console.WriteLine(this == null ? "Weird" : "Normal");
    }

    public virtual void Bar()
    {
        Console.WriteLine(this == null ? "Weird" : "Normal");
    }
}
like image 195
LukeH Avatar answered Sep 25 '22 14:09

LukeH


Not possible directly in C# code (unless you generate dynamic code with Reflection.Emit or similar techniques) but by using directly IL code it is possible to call a virtual method and having the this == null.

Take this code:

using System;

public class C {
    public virtual void M() {
        Console.WriteLine("Inside the method M. this == null: {0}", this == null);
    }
}

public class Program {
    public static void Main(string[] pars)
    {
        C obj = null;
        obj.M();
    }
}

save it to testnull.cs. From a Visual Studio command prompt, do:

csc.exe testnull.cs

ildasm.exe testnull.exe /out:testnull.il

then look in the testnull.il to this line of code:

callvirt   instance void C::M()

and change it to:

call   instance void C::M()

and save.

ilasm.exe testnull.il /out:testnull2.exe

now try running it:

testnull2.exe

and you'll get:

Inside the method M. this == null: True

if you want, the full il code is:

//  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.33440
//  Copyright (c) Microsoft Corporation.  All rights reserved.



// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly testnull
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                             63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module testnull.exe
// MVID: {D8510E3B-5C38-40B9-A5A2-7DAE75DE1642}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x00300000


// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit C
       extends [mscorlib]System.Object
{
  .method public hidebysig newslot virtual 
          instance void  M() cil managed
  {
    // Code size       22 (0x16)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Inside the method M. this == null: {0}"
    IL_0006:  ldarg.0
    IL_0007:  ldnull
    IL_0008:  ceq
    IL_000a:  box        [mscorlib]System.Boolean
    IL_000f:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object)
    IL_0014:  nop
    IL_0015:  ret
  } // end of method C::M

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method C::.ctor

} // end of class C

.class public auto ansi beforefieldinit Program
       extends [mscorlib]System.Object
{
  .method public hidebysig static void  Main(string[] pars) cil managed
  {
    .entrypoint
    // Code size       11 (0xb)
    .maxstack  1
    .locals init (class C V_0)
    IL_0000:  nop
    IL_0001:  ldnull
    IL_0002:  stloc.0
    IL_0003:  ldloc.0
    IL_0004:  call   instance void C::M()
    IL_0009:  nop
    IL_000a:  ret
  } // end of method Program::Main

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method Program::.ctor

} // end of class Program


// =============================================================

// *********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file testnull.res

Note that the code that must be written in IL code is the calling code (the code that does the call), not the called code (the virtual method that is called).

In the initial versions of the C# compiler (probably internal pre-alpha versions of C# 1.0... the change I'm speaking here was done at the end of 1999, while C# 1.0 was released in 2002), Microsoft programmers tried to sometimes generate call methods instead of callvirt methods (call calls don't do null checks, while callvirt calls do it), but after discovering that it was possible to make a call to an instance method having a this == null, they decided for always using callvirt for instance methods (see here).

like image 38
xanatos Avatar answered Sep 25 '22 14:09

xanatos