1
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.

cazicss
  • 31
  • 6
  • I was wondering if it's a template kinda thing. Object .. T.ToString() .. Since the compiler would know all the types at compile time. But that's me just guessing. – cazicss Sep 08 '17 at 17:10

3 Answers3

4

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.

Drew Noakes
  • 266,361
  • 143
  • 616
  • 705
  • it may be worth mentioning that `ToString()` doesnt actually unbox an object, so it doesnt matter what the type within is? – thanatorr Sep 08 '17 at 17:18
  • " to call a virtual function, you need to box the value type"... is false in the case of types used in the sample code. See https://stackoverflow.com/questions/18615648/does-valuetype-tostring-does-a-cast-on-the-valuetype for details (this optimization is possible for all sealed types obviously). – Alexei Levenkov Sep 08 '17 at 17:24
  • @Alexei but the value is boxed at the call site, since the parameter type is `object`, right? The call site doesn't know that `.ToString()` will be called in the method. The JITter might, though, or at least does so after the first call? – CodeCaster Sep 08 '17 at 17:27
  • 2
    @CodeCaster in code sample shown (with `object` as argument) it is too late to avoid boxing and indeed `Int32.ToString` will be called on boxed object as normal virtual method (it is somewhat confusing what OP mean as `object.ToString` as that particular method will never be invoked in the sample as all types used provide own overrides). My comment was about general statement - there are cases where virtual method call does not need boxing (or virtual method call in general) because exact method is known at compile/JIT time. – Alexei Levenkov Sep 08 '17 at 18:00
  • @Alexei Levenkov - Int32::ToString() is not virtual, so how could Object::ToString() resolve to Int32::ToString() without knowing the Object is boxing an Int32 ? ... i'm clearly missing something in the whole boxing process that makes this work. – cazicss Sep 08 '17 at 18:19
  • 2
    @cazicss, `Int32.ToString()` is virtual. It's an override, which is necessarily virtual. – Drew Noakes Sep 08 '17 at 19:16
  • `However to call a virtual function, you need to box the value type.` That's not true. You can call a virtual method of a value type without needing to box it. – Servy Sep 08 '17 at 19:16
  • @Servy, you can call a method which has a virtual specifier, but not in a virtual way. Technically interface methods don't exist on the type itself (hence explicit implementations not being member functions -- see recent talk on C# 8 by Mads). Without boxing, you can only make non-virtual calls to the type. Virtual calls require an object reference to satisfy the lookup. – Drew Noakes Sep 08 '17 at 19:22
  • @DrewNoakes But that's not what you said in your answer. In your answer you said that you can't call a virtual function without boxing the value. The correct statement is that a virtual function is only actually called through virtual dispatch when the object is boxed. The two statements are radically different. Your statement means that calling any virtual method on a non-boxed value type requires it to be boxed, which is not the case. – Servy Sep 08 '17 at 19:25
  • I wrote "virtual function", meaning a function call that is virtual. A function is just a bunch of instructions. The lookup is either static or virtual. Anyway, seems we both agree on the facts. – Drew Noakes Sep 08 '17 at 19:27
  • @DrewNoakes A virtual function and a virtual function call are radically different things, a virtual function most certainly does *not* mean the same thing as a virtual function call. If you agree with the facts, then you should correct the erroneous statement in your answer, rather than leaving an incorrect statement that you are aware is incorrect. – Servy Sep 08 '17 at 19:30
  • It might be worth adding something on the fact that non-virtual calls derived from `object` (`GetType()`) does require boxing, for completeness. – Jon Hanna Sep 08 '17 at 19:46
  • 1
    The reason being that since `int.ToString()` is an override it is defined as an operation on `int` and while it can be reached through the base virtual definition it can also be reached through an address as a value type method. `object.GetType()` in contrast is only defined in terms of `object` so boxing can't be avoided, though the compiler can at least optimise out the C# rule of generally using `callvirt` even for non-virtual calls to get the null-check, since it clearly can't be null so `call` can be safely used while remaining with C# rules of observed behaviour. – Jon Hanna Sep 08 '17 at 20:13
0

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.

InBetween
  • 30,991
  • 3
  • 46
  • 80
  • I didn't mean structs in general. I was under the impression that the primitives like Int32 (which are value types) did not have virtual functions because that would require a vtable pointer and increase the over all size of the Int32 ... perhaps I'm way off base here. – cazicss Sep 08 '17 at 19:17
  • The "value type" I'm talking about is specific to my question.. which is only Int32 and Double. – cazicss Sep 08 '17 at 19:18
  • @cazicss [System.Int32 Reference source](https://referencesource.microsoft.com/#mscorlib/system/int32.cs,147) `int` overrides `ToString()`. – InBetween Sep 08 '17 at 19:20
  • 1
    @cazicss C# has no concept of "primitives". It's not a term in the C# specs. `Int32` is a value type (it is special in certain ways, but none of them are relevant here). It has virtual methods (`ToString` is one of them`). If you have a compile time expression of type `int` and you call `ToString` on it, the compiler is smart enough to know that, even though thought the method is virtual, there is only one possible implementation, and so a non-virtual call can be made to that method. If the value is boxed in an `object`, then that can't be done, and a virtual call is needed, and happens. – Servy Sep 08 '17 at 19:20
  • How does the Int32::ToString() get called when calling obj.ToString() when I'm assuming Int32 has no virtual functions. – cazicss Sep 08 '17 at 19:20
  • @cazicss As you've been told, Int32 *does* have virtual functions, so your assumption is false. – Servy Sep 08 '17 at 19:21
  • @cazicss your assumption is simply wrong. See my previous comment. – InBetween Sep 08 '17 at 19:21
  • If int overrides ToString, wouldn't that make it larger than 4 bytes? – cazicss Sep 08 '17 at 19:21
  • @cazicss No. A type overriding a method has nothing to do with the size of each value of that type. – Servy Sep 08 '17 at 19:22
  • Unless the vtable pointer is only needed when you expect derived classes ? – cazicss Sep 08 '17 at 19:22
  • Alright I think a few mishap assumptions have been cleared up lol. If I can't wrap my brain around it from this I'll dig deeper into boxing/unboxing. Appreciate the effort <3 – cazicss Sep 08 '17 at 19:26
  • @cazicss you may want to read https://stackoverflow.com/questions/7074/what-is-the-difference-between-string-and-string-in-c (there is no "primitive types" in C#/.Net in the same way as in Java) – Alexei Levenkov Sep 08 '17 at 21:08
0

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/

cazicss
  • 31
  • 6