5

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

Xaisoft
  • 45,655
  • 87
  • 279
  • 432
  • Lets just say the runtime will create a "specialized" version of OutputScreen for each type it's used with. So if you do OutputScreen(5); OutputScreen(5.0); OutputScreen(string) three versions of OutputScreen will be created: OutputScreen(int) {}, OutputScreen(double) {}, OutputScreen("foo") {} and then each time the right one will be called. There won't be any boxing IN THE CALLING of OutputScreen because the right "overloaded" version will be present (created by the runtime). This is different in Java, where generics are implemented with erasure (everything is casted to object behind you) – xanatos Mar 16 '11 at 12:47
  • I'll add to my (space constrained) explanation (that should be considered simplified) a link to a similar question: http://stackoverflow.com/questions/4560890/what-is-generics-in-c – xanatos Mar 16 '11 at 12:56
  • If someone is asking questions about boxing (a fundamental complex), I'm not sure there's much value in muddying the waters with generics (powerful, but we might want to keep it simple and answer the question at hand). – RQDQ Mar 16 '11 at 13:04
  • 2
    @xanatos: Nice try but no cigar. **Overload resolution is not performed again at runtime**. Overload resolution only happens at **compile time**. Which means that in every generic construction, you'll have the *same* overload of Console.WriteLine, and the only one that can take an unconstrained T is the one that boxes. Remember, generics are *generic types*, not *compilation-time templates* like they are in C++. – Eric Lippert Mar 16 '11 at 14:12
  • @Eric Right... I had forgotten... And it happened to me various times :-) – xanatos Mar 16 '11 at 14:14

4 Answers4

13

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#

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
5

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);
RQDQ
  • 15,461
  • 2
  • 32
  • 59
  • But doesn't boxing still occur when you have call string temp = x.ToString() – Xaisoft Mar 16 '11 at 12:37
  • @Xaisoft - nope. Boxing is the process of storing a value type in a reference type. In this case, x.ToString() returns a string (which is a reference type), and that's being stored in a variable of type string (which is still a reference type). Therefore, no boxing. – RQDQ Mar 16 '11 at 12:43
  • @RQDQ: Your reasoning is specious. Boxing is NOT "the process of storing a value type in a reference type". Boxing is the process of **using** a value type as a reference type. By your logic, a call to x.GetType() does not box, but I assure you that it does. – Eric Lippert Mar 16 '11 at 14:14
  • I think I get it, but still a little slightly confused. Even if I do x.ToString(), there is not implicit cast there or boxing involved? – Xaisoft Mar 16 '11 at 14:15
  • @Eric Lippert, based on your comment, is passing in x of type integer to a method that takes an object considered boxing because I am using the value type x as a reference type, but if I do something like object o = x; this would not be boxing because I am storing a value type in a reference type. Can you elaborate? – Xaisoft Mar 16 '11 at 14:18
  • 3
    @Xaisoft: Boxing happens whenever a value of a value type is used in a context where the type of the expression must be a reference type. When an int is stored in a variable of type object, whether a local variable, a field, or a formal parameter of a call, then it must be boxed. But boxing happens not only when an value is stored in a variable of reference type; it happens any time that a value is *used* as a reference type. For example, when used as the receiver of a call to GetType(), or when cast to IDisposable, and so on. – Eric Lippert Mar 16 '11 at 14:38
  • @Eric: You said "Boxing is the process of using a value type as a reference type.". So is boxing the same as making a value type nullable? Are they exactly the same just exposed differently for users? I am just not sure if they are the same or different. – Joan Venge Mar 16 '11 at 17:24
  • 2
    @Joan: A nullable value type is nothing more than a struct containing pair of (T, bool) where the bool means "is it not null?". That is, "**struct** Nullable{ bool HasValue; T t; }" and the appropriate accessors, etc. A nullable int is just a convenient way of passing around an int, plus a flag that indicates whether the int is null or not. Everything else is just compiler and runtime trickery. A boxed value type is basically "**class** Box{ T t; }" where the class also implements the interfaces that T implements, is convertible back to T, and so on. – Eric Lippert Mar 16 '11 at 20:03
  • @Eric: Thanks Eric. Now that makes sense. I always thought Nullable was a class, not a struct. You guys could have implemented this as a class, right? If so, why did you decide to use a struct for Nullable? I assume this is for performance reasons so a nullable version of a value type isn't any slower than non-nullable version? – Joan Venge Mar 16 '11 at 20:49
  • 2
    @Joan: If a type is logically a *value* then a nullable type is also logically a *value*. It would seem very strange to have "decimal" be a value type, and a type that has *every single value of decimal plus null* be a reference type. – Eric Lippert Mar 16 '11 at 22:22
  • @Eric: Thanks Eric. You are right. Didn't realize you did it because of correctness. I wouldn't find it very strange if it was like that, because I would think this value can now be null, so is not unreasonable to have it as a reference type. But I don't know it like you, so mine is just what I thought would be reasonable from implementation POV. – Joan Venge Mar 16 '11 at 22:26
3

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.

MrQuery
  • 35
  • 6
2

Because in the second case you're passing a System.String, which is a reference type and thus doesn't need boxing.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299