How does object.ToString() work on a boxed value type?

734 views Asked by At
class Program {

    static void Main(string[] args) {
        Int32 i = 123;
        Double d = 123.456;
        FunPrint(i);
        FunPrint(d);
    }

    static void FunPrint(object obj) {
        Console.WriteLine(obj);
    }
}

My understanding of this sample is that FunPrint() first creates a new object and builds it based on the value of the ValueType being passed (Int32 in this case). Second, the Object.ToString() is called and correctly displays ValueType-specific string formatting.

Value types do not contain virtual functions so...

What I do not understand is how the Object knows what type it is holding internally in order to do the proper string formatting.

3

There are 3 answers

13
Drew Noakes On BEST ANSWER

The calling function boxes the argument before calling FunPrint.

Value types do not contain virtual functions so...

Actually they can. You can implement an interface from a value type. You just can't derive from one, which limits the level of overriding.

However to call a virtual function function virtually, you need to box the value type.

The mechanism applies equally here. The value is already boxed, so you can call its virtual members.


Edit to clarify calling interface methods on a value types:

var i = 123;
i.ToString();                // not boxed, statically resolves to Int32.ToString()

var o = (object)o;           // box
o.ToString();                // virtual call to object.ToString()

var e = (IEquatable<int>)i;  // box
i.Equals(123);               // virtual call to IEquatable<int>.Equals(int)

Edit to include suggestion from Jon Hanna. Calling a non-virtual method of System.Object on a value type does require boxing.

var i = 1234;
i.GetType();    // boxes!

You can see that in the corresponding IL:

ldc.i4.s     123
stloc.0      // i
ldloc.0      // i
box          [mscorlib]System.Int32
call         instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

Object.GetType() is not able to be called virtually, and has signature:

public extern Type GetType();

Yet it still requires boxing.

0
cazicss On

For others that come to this and equally bash their heads against box/unbox magic, I have found some solid further reading on the topic here:

http://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/

12
InBetween On

Value types do not contain virtual functions so...

Really?

struct Foo
{
     public override string ToString() =>
         "Sure looks like a virtual call";

     public override bool Equals(object obj) =>
         "So does this one";
}

All value types inherit from object and you can override any virtual method perfectly fine. The fact that you can't extend them any further is irrelevant.