Whose ToString() would be called?
Asked Answered
M

4

5

As we know, int has ToString() method which override the ToString() method of base type Object.

For this following code,

int x = 100;
object y = (object)x;
Console.Write(y.ToString());

(1) Whose ToString() would be called? int or object? WHY?
(2) How we can check/view the truth? By any debug/tool?

Mocambique answered 2/8, 2012 at 6:25 Comment(1)
Just check whether tostring is declared as virtual or not in object. What you are asking is the very definition of virtual functions.Unwish
H
3

Since the value is boxed, all the compiler knows about is object, so it is a regular virtual call to object.ToString(), which will then pick up the overridden ToString() of the struct. So: it is object.ToString() that is invoked, and the Int32.ToString() override which is executed.

private static void Main()
{
    int x = 100;
    object y = (object)x;
    Console.Write(y.ToString());
}

becomes (comments mine):

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 x,
        [1] object y)

    // int x = 100;
    L_0000: ldc.i4.s 100
    L_0002: stloc.0

    // object y = (object)x; 
    L_0003: ldloc.0 
    L_0004: box int32
    L_0009: stloc.1

    // Console.Write(y.ToString());
    L_000a: ldloc.1 
    L_000b: callvirt instance string [mscorlib]System.Object::ToString()
    L_0010: call void [mscorlib]System.Console::Write(string)
    L_0015: ret 
}

The important line is at L_000b; a regular virtual-call to object.ToString().

What gets more interesting is non-boxed value-types; if a value-type is known to have a ToString(), then it can emit a static call:

private static void Main()
{
    int x = 100;
    Console.Write(x.ToString());
}

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 x)
    L_0000: ldc.i4.s 100
    L_0002: stloc.0 
    L_0003: ldloca.s x
    L_0005: call instance string [mscorlib]System.Int32::ToString()
    L_000a: call void [mscorlib]System.Console::Write(string)
    L_000f: ret 
}

See the static-call (call) at L_0005. HOWEVER, in most value-type cases it will use a constrained call, which will be interpreted by the JIT as a static-call if it is overridden, and a virtual-call if it isn't:

private static void Main()
{
    var x = new KeyValuePair<int, string>(123, "abc");
    Console.Write(x.ToString());
}

becomes:

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string> x)
    L_0000: ldloca.s x
    L_0002: ldc.i4.s 0x7b
    L_0004: ldstr "abc"
    L_0009: call instance void [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string>::.ctor(!0, !1)
    L_000e: ldloca.s x
    L_0010: constrained [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string>
    L_0016: callvirt instance string [mscorlib]System.Object::ToString()
    L_001b: call void [mscorlib]System.Console::Write(string)
    L_0020: ret 
}

The "constrained" / "callvirt" pair at L_0010 and L_0016 together make this construct, so it might not actually be a virtual call. The JIT / runtime can do other voodoo on it. This is discussed more here.

Note that a regular class will always use virtual call for this, except for the scenario return base.ToString();, which is a static-call to the base-types implementation.

Hubert answered 2/8, 2012 at 7:44 Comment(4)
So: it is object.ToString() that is invoked, and the Int32.ToString override which is executed. -----------------------------------------------------------So, we can say, the answer for my question is: Int32's ToString() is called ??? (2) What does you mean "invoked"/"executed", are you talking about the underlying methodology of the design&implementation of inheritence???Mocambique
@Mocambique yes, I'm talking about the nature of polymorphism. There is a split between what is requested (the base-type call) and what actually happens (the most-overridden call).Hubert
Say... a regular simple polymorphism sample.. i have a base class A, a derived class B from A. In B, there is an override method M(). 'A a = new B(); a.M();' What happend? Also always need A.M() "invoked"???? Can you explain a bit more about "invoked"?Mocambique
@Mocambique the compiler refers to A.M(), via virtual-call. At runtime, the method-table data for B (as the instance-type) is substituted during the call, so B.M() is executed. Just polymorphism, really.Hubert
M
4

Int32.ToString() would be called, because ToString() is called virtually. Int32.ToString() overrides Object.ToString(), so at runtime it effectively replaces Object.ToString().
And at runtime, y is a boxed int.

Maggs answered 2/8, 2012 at 6:28 Comment(2)
@Erno I think it is. At least the output 100 that the above snippet produces suggests it.Maggs
@Erno once boxed, it is a virtual callHubert
H
3

Since the value is boxed, all the compiler knows about is object, so it is a regular virtual call to object.ToString(), which will then pick up the overridden ToString() of the struct. So: it is object.ToString() that is invoked, and the Int32.ToString() override which is executed.

private static void Main()
{
    int x = 100;
    object y = (object)x;
    Console.Write(y.ToString());
}

becomes (comments mine):

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 x,
        [1] object y)

    // int x = 100;
    L_0000: ldc.i4.s 100
    L_0002: stloc.0

    // object y = (object)x; 
    L_0003: ldloc.0 
    L_0004: box int32
    L_0009: stloc.1

    // Console.Write(y.ToString());
    L_000a: ldloc.1 
    L_000b: callvirt instance string [mscorlib]System.Object::ToString()
    L_0010: call void [mscorlib]System.Console::Write(string)
    L_0015: ret 
}

The important line is at L_000b; a regular virtual-call to object.ToString().

What gets more interesting is non-boxed value-types; if a value-type is known to have a ToString(), then it can emit a static call:

private static void Main()
{
    int x = 100;
    Console.Write(x.ToString());
}

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 x)
    L_0000: ldc.i4.s 100
    L_0002: stloc.0 
    L_0003: ldloca.s x
    L_0005: call instance string [mscorlib]System.Int32::ToString()
    L_000a: call void [mscorlib]System.Console::Write(string)
    L_000f: ret 
}

See the static-call (call) at L_0005. HOWEVER, in most value-type cases it will use a constrained call, which will be interpreted by the JIT as a static-call if it is overridden, and a virtual-call if it isn't:

private static void Main()
{
    var x = new KeyValuePair<int, string>(123, "abc");
    Console.Write(x.ToString());
}

becomes:

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string> x)
    L_0000: ldloca.s x
    L_0002: ldc.i4.s 0x7b
    L_0004: ldstr "abc"
    L_0009: call instance void [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string>::.ctor(!0, !1)
    L_000e: ldloca.s x
    L_0010: constrained [mscorlib]System.Collections.Generic.KeyValuePair`2<int32, string>
    L_0016: callvirt instance string [mscorlib]System.Object::ToString()
    L_001b: call void [mscorlib]System.Console::Write(string)
    L_0020: ret 
}

The "constrained" / "callvirt" pair at L_0010 and L_0016 together make this construct, so it might not actually be a virtual call. The JIT / runtime can do other voodoo on it. This is discussed more here.

Note that a regular class will always use virtual call for this, except for the scenario return base.ToString();, which is a static-call to the base-types implementation.

Hubert answered 2/8, 2012 at 7:44 Comment(4)
So: it is object.ToString() that is invoked, and the Int32.ToString override which is executed. -----------------------------------------------------------So, we can say, the answer for my question is: Int32's ToString() is called ??? (2) What does you mean "invoked"/"executed", are you talking about the underlying methodology of the design&implementation of inheritence???Mocambique
@Mocambique yes, I'm talking about the nature of polymorphism. There is a split between what is requested (the base-type call) and what actually happens (the most-overridden call).Hubert
Say... a regular simple polymorphism sample.. i have a base class A, a derived class B from A. In B, there is an override method M(). 'A a = new B(); a.M();' What happend? Also always need A.M() "invoked"???? Can you explain a bit more about "invoked"?Mocambique
@Mocambique the compiler refers to A.M(), via virtual-call. At runtime, the method-table data for B (as the instance-type) is substituted during the call, so B.M() is executed. Just polymorphism, really.Hubert
B
2

Int32, because that's the actual object underneath satisfying the same contract. You can check by watching the output. If this wouldn't be the case you would have "System.Object" returned instead.

Beverlybevers answered 2/8, 2012 at 6:29 Comment(2)
But after we boxing an Int32 to object, we get another object whose GetType() is "System.Int32". So that's the reason???Mocambique
@Mocambique GetType() will return "System.Int32" even without boxing. It returns you the type of the variable at runtime.Maggs
C
0

Thought that was java. Sorry! :) int is a primitive type. All primitive types, are the only exceptions where the magic of OO stops. They dont inherit object, they are not objects!(no toString method, no methods in general)

Integer, has the toString method. If your example was with Integer, instead of int, and your object was casted to a class(like you did in your example), if that class overrides toString(eg was your custom class), or just already had toString method(like Integer class), that class toString method should be called.

Otherwise, the toString method of the class who inherited from should be called. If didnt inherited from nobody(means: inherited from Object class), object class toString should be called.

Writing a simple program with some prints can help you:

Integer x = new Integer(3);

print:

x.toString()
((Object) x).toString();

And new anonymous class, same as Integer, but toString Overriden

Integer y = new Integer(3){
    @Override
    String toString(){
    return "WHATEVER!!";

    }
    };

print:

y.toString();
Cleveite answered 2/8, 2012 at 6:32 Comment(5)
Actually, int/Int32 inherit from object via System.ValueType. I guess you're thinking of java, where there is a difference between int and Integer. In .NET, int and System.Int32 are the same and they are full .NET Types (boxing is done by casting to object).Maggs
Indeed! thanks Botz. Thats very close to Java! :) Only changed thing was ToString, T was capital!Cleveite
@Pashalis What i meant to say is that there are no primitive types in .NET/C#. This question is not about java. In .NET, every type, even int, derives from object. And there are no wrapper types for primitive types, since there are no primitive types. So the first two lines are not correct :)Maggs
@Botz3000, i understood you. You are right! What i wanted to say in my previous comment is that i got confused because i saw only title, and body of question, without seeing the tags! And i answered!! Just curious: other lines are correct in C#?Cleveite
Ah, i see :) Yes, except that you can't derive from Int32 in .NET, as you are doing with the anonymous class, because Int32 derives from ValueType and all types deriving from ValueType are sealed (not inheritable). And the override annotation is not used, instead it would be public override string ToString().Maggs

© 2022 - 2024 — McMap. All rights reserved.