Oday Fraiwan
Oday Fraiwan

Reputation: 1157

Boxing of user-defined value types

According to MSDN, if a struct were defined, that struct should override all methods inherited from the object class. That is recommended to avoid the unnecessary boxing when calling any inherited method such as ToString.

According to the MSDN, to determine if and when boxing occurs, an IL instruction "box" can be found in the MSIL code.

I wrote the following test to see the boxing.

using System;

namespace TestingBoxing
{
    public struct StructX
    {
        public int member1;
        public int member2;
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            StructX s1;

            s1.member1 = 2;
            s1.member2 = 5;

            string str = s1.ToString();

            Console.WriteLine(str);
        }
    }
}

However, the boxing instruction cannot be seen in the MSIL code below although ToString is called without being overridden in the struct definition.

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       37 (0x25)
  .maxstack  2
  .locals init ([0] valuetype TestingBoxing.StructX s1,
           [1] string str)
  IL_0000:  ldloca.s   s1
  IL_0002:  ldc.i4.2
  IL_0003:  stfld      int32 TestingBoxing.StructX::member1
  IL_0008:  ldloca.s   s1
  IL_000a:  ldc.i4.5
  IL_000b:  stfld      int32 TestingBoxing.StructX::member2
  IL_0010:  ldloca.s   s1
  IL_0012:  constrained. TestingBoxing.StructX
  IL_0018:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_001d:  stloc.1
  IL_001e:  ldloc.1
  IL_001f:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0024:  ret
} // end of method Program::Main 

How that could be explained ?

Reference article: http://msdn.microsoft.com/en-us/library/ms973858.aspx#code-snippet-6

Upvotes: 3

Views: 423

Answers (2)

Stilgar
Stilgar

Reputation: 23561

In my opinion it is the callvirt instruction that does the boxing. Looking at the disassembly for your code we get this disassembly on the line where ToString is called

00DB287A  mov         ecx,26933C0h  
00DB287F  call        00AD2100  
00DB2884  mov         dword ptr [ebp-18h],eax  
00DB2887  mov         edi,dword ptr [ebp-18h]  
00DB288A  add         edi,4  
00DB288D  lea         esi,[ebp-10h]  
00DB2890  movq        xmm0,mmword ptr [esi]  
00DB2894  movq        mmword ptr [edi],xmm0  
00DB2898  mov         ecx,dword ptr [ebp-18h]  
00DB289B  mov         eax,dword ptr [ecx]  
00DB289D  mov         eax,dword ptr [eax+28h]  
00DB28A0  call        dword ptr [eax]  
00DB28A2  mov         dword ptr [ebp-1Ch],eax  
00DB28A5  mov         eax,dword ptr [ebp-1Ch]  
00DB28A8  mov         dword ptr [ebp-14h],eax 

And if we change the code to:

public struct StructX
{
    public int member1;
    public int member2;

    public override string ToString()
    {
        return member1.ToString() + " " + member2.ToString();
    }
}

We get:

02352875  lea         ecx,[ebp-8]  
02352878  call        dword ptr ds:[4DD33E0h]  
0235287E  mov         dword ptr [ebp-10h],eax  
02352881  mov         eax,dword ptr [ebp-10h]  
02352884  mov         dword ptr [ebp-0Ch],eax  

Now my assembly is quite rusty but it seems to me that all this moving around is actually boxing. When the type is a value type The C# compiler can skip call virtual since it is certain that the method cannot be overridden in a derived type.

Edit: As pointed by other answers the callvirt is still there it is the CLR that does the optimization.

Upvotes: 1

Gary.S
Gary.S

Reputation: 7121

This can be explained by looking at what Constrained does.

A field is typically constrained in order to use callvirt in a standard way without needing to explicitly box. It does the following:

If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.

If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.

If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.

What this means is (as stated by the MSDN article):

This last case can occur only when method was defined on Object, ValueType, or Enum and not overridden by thisType. In this case, the boxing causes a copy of the original object to be made. However, because none of the methods of Object, ValueType, and Enum modify the state of the object, this fact cannot be detected.

Emphasis mine. Basically saying that if boxing does occur, it cannot be determined via IL.

Constrained MSDN: http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained%28v=vs.110%29.aspx

Upvotes: 6

Related Questions