Question

What is the difference between two variable's ToString Calling ?

int i = 0;
i.ToString();

Does calling i.ToString() will make i first boxed then call ToString or i is already boxed before calling ToString() ?

Was it helpful?

Solution

int is an alias of System.Int32 struct type (implicitly sealed) which has a method Int32.ToString() and this is a method called in the second line of your code so no type conversion occurs.

System.Int32 is derived from System.ValueType which is derived from System.Object. Int32.ToString() overrides ValueType.ToString() which overrides Object.ToString().

The best way to check whether boxing occurs is to see IL code (I've been using ILSpy):

using System;

namespace BoxingTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            string s1 = i.ToString();
        }
    }
}

is translated to:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 12 (0xc)
    .maxstack 1
    .entrypoint
    .locals init (
        [0] int32 i,
        [1] string s1
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: ldloca.s i
    IL_0005: call instance string [mscorlib]System.Int32::ToString()
    IL_000a: stloc.1
    IL_000b: ret
} // end of method Program::Main

You can see that no boxing occurred and that System.Int32::ToString() was called.

Boxing would have taken place if e.g. you casted your int to object explicitly or implicitly. (Note that conversion to object type is not the only case when boxing happens)

Explicit casting to type object:

using System;

namespace BoxingTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            string s2 = ((object)i).ToString();
        }
    }
}

gives:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 16 (0x10)
    .maxstack 1
    .entrypoint
    .locals init (
        [0] int32 i,
        [1] string s2
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: box [mscorlib]System.Int32
    IL_0009: callvirt instance string [mscorlib]System.Object::ToString()
    IL_000e: stloc.1
    IL_000f: ret
} // end of method Program::Main

Boxing through implicit cast:

using System;

namespace BoxingTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            object o = i;
            string s3 = o.ToString();
        }
    }
}

gives:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 18 (0x12)
    .maxstack 1
    .entrypoint
    .locals init (
        [0] int32 i,
        [1] object o,
        [2] string s3
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: box [mscorlib]System.Int32
    IL_0009: stloc.1
    IL_000a: ldloc.1
    IL_000b: callvirt instance string [mscorlib]System.Object::ToString()
    IL_0010: stloc.2
    IL_0011: ret
} // end of method Program::Main

In all three cases strings will have value "0" because Object.ToString() knows the original type of the boxed variable and calls ToString()` of that type.

This is the IL code of the Object.ToString():

.method public hidebysig newslot virtual 
    instance string ToString () cil managed 
{
    .custom instance void __DynamicallyInvokableAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x2052
    // Code size 12 (0xc)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: call instance class System.Type System.Object::GetType()
    IL_0006: callvirt instance string System.Object::ToString()
    IL_000b: ret
} // end of method Object::ToString

OTHER TIPS

Technically int inherits from System.ValueType which intern inherits from object. But to answer your question, there is no performance penalty. All Value Types are sealed types, they an neither derive nor be derived from. So, although as Hamlet Hakobyan points out, ToString is overridden by int, it is effectively sealed and no virtual dispatch, which would require boxing is performed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top