tags:

views:

284

answers:

3

Hi I've often wondered if the following scenario actually happens in c#

If I have a struct but I don't explicitly override any of the methods that derived from object such as ToString(), GetHashCode(), etc then if I declare a local instance of my struct class and call 'ToString()' on it, would my struct get boxed i.e would the CLR convert it implicitly to an object on the heap and then call ToString()? Or is it clever enough to know that there's no implementation for that struct and ignore it?

i.e

public struct Vector2D
{
    public float m_x;
    public float m_y;


    ...... etc
}


void SomeFunc()
{
  Vector2D aVec = new Vector2D();
  Console.WriteLine(aVec.ToString()); // <-- does aVec get boxed here?
  ..... 
}

== Edit - Update== Mehrdad's link to MSDN, whilst being useful has confused me slighly. I'll quote and see if any one can unpick this for me

When a callvirt method instruction has been prefixed by constrained thisType, the instruction is executed as follows:

If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.

If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.

If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.

So does that mean that if I don't explicitly implement ToString() on my struct type that it will fall into the last case and get boxed? Or am I mis-understanding it somewhere?

+3  A: 

No it's not boxed when you call ToString or GetHashCode if it's implemented by your struct (why should it? constrained IL instruction takes care of it.) It's boxed when you call a non-virtual method (or a virtual method not overriden in the struct) on System.Object (its base class), i.e. GetType/MemberwiseClone.

UPDATE: Sorry for the misunderstanding it may have caused. I wrote the answer with overriding the methods in the struct in mind (that's why I mentioned non-virtual methods need boxing, I should have been more explicit not to confuse readers, especially since I missed your statement regarding not overriding the method) as if you don't override it, the Object.ToString method expects it's first argument (reference to this) to be a reference type (an Object instance). Obviously, the value has to be boxed in that call (as it's a call in the base class.)

However, the point is, the nature of calling a virtual method on a value type does not result in emitting the box instruction (unlike non-virtual methods on Object that always result in emitting an explicit box instruction.) It's the callvirt instruction that will do the boxing if it has to resort to the Object.ToString implementation (as you mentioned in the updated question) just like when you are passing a struct to a method that expects an object parameter.

Mehrdad Afshari
Thanks for the link v useful
zebrabox
-1 This is definetly not true. See my answer.
kek444
The point is - does it get boxed? The question who/what started the Box operation is insignificant compared to the actual Box operation performance, which is the issue here.
kek444
kek444: Thanks for pointing it out; Fixed. I had missed the OP's primary point and thought "... etc" is method implementation.
Mehrdad Afshari
kek444: Yes. Your answer is correct. Effectively, a value type will get boxed, if and only if the method implementation is in `System.Object`. However, virtual and non-virtual methods behave totally differently at the IL level. I think the question was much simpler than I initially thought.
Mehrdad Afshari
No problem. I think a couple of people mis-read my question which implies I probably could have phrased it better
zebrabox
zebrabox: Nope. It was completely my fault. You explicitly mentioned not overriding it. The simple answer for the question is kek444's. I'd accept that one. However, I'll leave this answer for future readers who might be interested in the implementation detail.
Mehrdad Afshari
@Mehrdad : Thanks for the clarification and some useful info in there which gives me an excuse to actually read up on IL :)
zebrabox
+2  A: 

Edit: kek444's answer is correct. My apologies for misreading the question. I leave my answer here as I believe it has additional value and relevant information for future readers.

I also think that this quote from the reference in Mehrdad's answer is particularly thought provoking:

  • If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.

This last case can occur only when method was defined on Object, ValueType, or Enum and not overridden by thisType. In this case, the boxing causes a copy of the original object to be made. However, because none of the methods of Object, ValueType, and Enum modify the state of the object, this fact cannot be detected.

One cannot, therefore, write a program to demonstrate that the boxing is taking place. It is only discernible by looking at the IL and completely understanding the constrained prefix for the callvirt instruction.


From section 11.3.5 of the C# language specification at http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc (http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx):

When a struct type overrides a virtual method inherited from System.Object (such as Equals, GetHashCode, or ToString), invocation of the virtual method through an instance of the struct type does not cause boxing to occur. This is true even when the struct is used as a type parameter and the invocation occurs through an instance of the type parameter type. For example:

using System;
struct Counter
{
    int value;
    public override string ToString() {
     value++;
     return value.ToString();
    }
}
class Program
{
    static void Test<T>() where T: new() {
     T x = new T();
     Console.WriteLine(x.ToString());
     Console.WriteLine(x.ToString());
     Console.WriteLine(x.ToString());
    }
    static void Main() {
     Test<Counter>();
    }
}

The output of the program is:

1
2
3

Although it is bad style for ToString to have side effects, the example demonstrates that no boxing occurred for the three invocations of x.ToString().

Similarly, boxing never implicitly occurs when accessing a member on a constrained type parameter. For example, suppose an interface ICounter contains a method Increment which can be used to modify a value. If ICounter is used as a constraint, the implementation of the Increment method is called with a reference to the variable that Increment was called on, never a boxed copy.

using System;
interface ICounter
{
    void Increment();
}
struct Counter: ICounter
{
    int value;
    public override string ToString() {
     return value.ToString();
    }
    void ICounter.Increment() {
     value++;
    }
}
class Program
{
    static void Test<T>() where T: ICounter, new() {
     T x = new T();
     Console.WriteLine(x);
     x.Increment();      // Modify x
     Console.WriteLine(x);
     ((ICounter)x).Increment();  // Modify boxed copy of x
     Console.WriteLine(x);
    }
    static void Main() {
     Test<Counter>();
    }
}

The first call to Increment modifies the value in the variable x. This is not equivalent to the second call to Increment, which modifies the value in a boxed copy of x. Thus, the output of the program is:

0
1
1

For further details on boxing and unboxing, see §4.3.

GBegen
Thanks for the answer but my question was actually what would happen if you didn't override toString()
zebrabox
Yep, good point about having to know the IL. It doesn't explicitly emit a box instruction so the 'box' is happening as a result of calling the base class implementation throughcallvirt instance string [mscorlib]System.Object::ToString()
zebrabox
+5  A: 

If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.

This last case can occur only when method was defined on Object, ValueType, or Enum and not overridden by thisType. In this case, the boxing causes a copy of the original object to be made.

The answer is yes, the value type is boxed. This is why it is always a good thing to override ToString() on custom structs.

kek444
Yep see my edited post - I think you're right on this one
zebrabox
+1 for reading the question correctly.
Mehrdad Afshari
@zebrabox: Unless you aren't calling `ToString()` on the object. :)
280Z28