Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objects based on nullable types are only boxed if the object is non-null

Tags:

c#

ildasm

I have created a simple C# program:

class Program
{
    static void Main(string[] args)
    {
        Int32? a = null;
        object x = a;
    }
}

According to MSDN:

Objects based on nullable types are only boxed if the object is non-null. If HasValue is false, the object reference is assigned to null instead of boxing.

I have tried my executable in ILDASM and spotted that IL code has box method called.

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       17 (0x11)
  .maxstack  1
  .locals init ([0] valuetype [mscorlib]System.Nullable`1<int32> a,
           [1] object x)
  IL_0000:  nop
  IL_0001:  ldloca.s   a
  IL_0003:  initobj    valuetype [mscorlib]System.Nullable`1<int32>
  IL_0009:  ldloc.0
  IL_000a:  box        valuetype [mscorlib]System.Nullable`1<int32>
  IL_000f:  stloc.1
  IL_0010:  ret
} // end of method Program::Main

My question is: Why it was called? Maybe I am doing something wrong or misunderstand something?

like image 865
J.King Avatar asked Jan 07 '23 08:01

J.King


2 Answers

The attempt to box a value does not mean it actually is a boxed value - x will be null, not a boxed null. I think the MSDN is trying to explain that:

Int32? a = null;
object x = a;
object y = a;
object.ReferenceEquals(x, y); // true

But:

Int32? a = 3;
object x = a;
object y = a;
object.ReferenceEquals(x, y); // false

As for compiling in release mode - it might not attempt to box the value because at compile time it is known that a is null - if a was a parameter of a public method, it will probably always attempt to box the value (but never actually box null).

like image 65
C.Evenhuis Avatar answered Jan 09 '23 21:01

C.Evenhuis


You're compiling your code in Debug mode. Change it to Release and you'll get the desired behavior. In this case, it'll omit the assigment all together:

.method private hidebysig static 

    void Main (string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2054
    // Code size 11 (0xb)
    .maxstack 1
    .locals init (
        [0] valuetype [mscorlib]System.Nullable`1<int32>
    )
    IL_0000: ldloca.s 0
    IL_0002: initobj valuetype [mscorlib]System.Nullable`1<int32>
    IL_0008: ldloc.0
    IL_0009: pop
    IL_000a: ret
} // end of method C::Main

If you change your code a bit and attempt to accept a Int? as a parameter to a method, and explicitly pass null at compile time, the compiler in Release mode will emit a box instruction.

Given:

public void M(int? x) 
{
    object y = x;
    Console.WriteLine(y);
}
public void Main()
{
    M(null);
}

You'll see:

.method public hidebysig 
 instance void M (
        valuetype [mscorlib]System.Nullable`1<int32> x
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 12 (0xc)
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: box valuetype [mscorlib]System.Nullable`1<int32>
    IL_0006: call void [mscorlib]System.Console::WriteLine(object)
    IL_000b: ret
} // end of method C::M

.method public hidebysig 
 instance void Main () cil managed 
{
    // Method begins at RVA 0x2060
    // Code size 16 (0x10)
    .maxstack 2
    .locals init (
        [0] valuetype [mscorlib]System.Nullable`1<int32>
    )
    IL_0000: ldarg.0
    IL_0001: ldloca.s 0
    IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32>
    IL_0009: ldloc.0
    IL_000a: call instance void C::M(valuetype [mscorlib]System.Nullable`1<int32>)
    IL_000f: ret
} // end of method C::Main
like image 38
Yuval Itzchakov Avatar answered Jan 09 '23 23:01

Yuval Itzchakov