Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How passing in x.ToString() into a method which is expecting an object type as opposed to just x prevent boxing?

Tags:

c#

I have a method called OutputToScreen(object o) and it is defined as:

public void OutputToScreen(object o)
{
    Console.WriteLine(o.ToString());
}

In my main calling method, if I do something like:

int x = 42;
OutputToScreen(x); // x will be boxed into an object

But if I do,

OutputToScreen(x.ToString()); // x is not boxed

I am still not sure why x is not boxed in the second approach, I just saw it on a free video from quickcert. Can someone give good explanation?

Here is an additional question based on comments:

If I pass in x.ToString() which is similar to doing:

string temp = x.ToString(); and then passing in temp, doesn't boxing still occur when I box x to a string type

like image 619
Xaisoft Avatar asked Mar 16 '11 12:03

Xaisoft


3 Answers

None of the answers or comments so far give an accurate or germane explanation of the relevant portion of the question. It is clear that there is no boxing when the string is passed to OutputToScreen. The relevant question is why there is no boxing when the string is produced by the call to ToString, a virtual method on object.

First off, let's consider the premise of the question. Your question is motivated by the assumption that you gain by avoiding boxing. That is, either (1) you box, and then call ToString, and then display the result, or (2) you skip the boxing, call ToString, and display the result. Though that does sound like a win, you have to consider the win in context. Converting to string and displaying to screen are both slow compared to boxing; trying to make the program faster by avoiding boxing is like trying to get your cable bill paid faster by running to the post office instead of walking.

Ultimately any performance question should be settled by trying it both ways and measuring the effect in the context of a performance metric relevant to the user.

However, the question is still of general interest even if the effect of avoiding boxing is irrelevant for performance reasons.

Your question seems to be predicated on an incorrect understanding of how boxing works:

doesn't boxing still occur when I box x to a string type?

There is no such thing as boxing "to a string type". An int can box to a boxed int. That's all that an int can box to. A double can box to a boxed double. And so on.

The real question of interest that you should be asking is:

Why does calling ToString(), a virtual method declared on object, not box int to object in order to pass an object as "this" to the call?

The answer is straightforward. When a struct provides an accessible implementation of a virtual method, and you call that virtual method on the struct, then the C# compiler knows that the virtual method is not overridden anywhere else, because structs are all sealed. Since the compiler knows exactly what virtual method is being called, and exactly what the "this" reference should be for that method, it can generate code that calls that method directly, without boxing and subsequently looking up the method in the vtable.

int does provide a public override of ToString, and therefore calling ToString on an int does not box the int, look up ToString in the vtable of a boxed int, and then call the int's ToString on the boxed int. The compiler skips the middleman and goes straight to the call.

Now, suppose the method is not virtual. GetType() is not virtual. When you call GetType() on an int, the compiler cannot reason like that. It knows that there is only one implementation of GetType(), that it is on System.Object, and that it expects an object. So it boxes.

Now that you understand all that, you can understand the behaviour of this crazy program fragment:

int? x = null;
Console.WriteLine(x.ToString());
Console.WriteLine(x.GetType());

What does that do, and why?

int? overrides ToString, so the call to ToString succeeds. But GetType is not virtual, so x is boxed. There are no boxed nullable value types; a null int? boxes to a null reference. Therefore this calls null.GetType() and throws a null reference exception. Boxing can hide in places you don't expect!

If you make up your own struct:

struct S { /* does not override ToString */ }

then a call to

S s = new S();
string str = s.ToString();

will box, because only the object version is available, and that takes an object as "this".

Coincidentally, a variation on your question was the subject of my blog this past Monday. See that article for more details.

http://blogs.msdn.com/b/ericlippert/archive/2011/03/14/to-box-or-not-to-box-that-is-the-question.aspx

What is the vtable?

The vtable is the mechanism whereby virtual methods are looked up at runtime. See this answer from yesterday for a sketch of how the vtable works:

Virtual Functions C#

like image 105
Eric Lippert Avatar answered Oct 11 '22 13:10

Eric Lippert


UPDATE

After looking up the meaning of the word "specious", I did some more digging. This site provided some excellent IL level breakdown:

http://weblogs.asp.net/ngur/archive/2003/12/16/43856.aspx

It's true that there is no boxing in the case of x.ToString(), but the reasons appear to be much more subtle.

Apparently, in the case of structs (aka value types), a call to ToString() may or may do boxing at runtime. If the struct overrides the implementation of ToString, then calling ToString() is not considered boxing.

In the specific case of Int32, that method is indeed overridden:

public override string ToString()
{
   return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);
}

In the first case, an integer is being treated as a reference type (in this case by storing it in a reference type variable):

int x = 42;
object boxed = x;
OutputToScreen(boxed);

In the second case, you're not actually passing x to OutputToScreen. Your passing the result of x.ToString() to OutputToScreen, which is already a reference type (i.e. it's already an object). Also, the struct involved (Int32 in this case) has overridden ToString, so the call to ToString() isn't considered boxing. The return type of this method is a reference type already, so passing temp into OutputToScreen isn't boxing either.

int x = 42;
string temp = x.ToString();
OutputToScreen(temp);
like image 32
RQDQ Avatar answered Oct 11 '22 13:10

RQDQ


In the second example your passing a string to OutputToScreen which is already a reference type and so does not have to be boxed (ToString is executed first, the result, a string, is passed to OutputToScreen). With the first example you are passing an int to OutputToScreen so it has to be boxed.

like image 20
MrQuery Avatar answered Oct 11 '22 12:10

MrQuery