views:

126

answers:

4

Take this code:

interface ISomeInterface
{
    public int SomeProperty { get; }
}

struct SomeStruct : ISomeInterface
{
    int someValue;

    public int SomeProperty { get { return someValue; } }

    public SomeStruct(int value)
    {
        someValue = value;
    }
}

and then I do this somewhere:

ISomeInterface someVariable = new SomeStruct(2);

is the SomeStruct boxed in this case?

+10  A: 

Yes, it is. Basically whenever you need a reference and you've only got a value type value, the value is boxed.

Here, ISomeInterface is an interface, which is a reference type. Therefore the value of someVariable is always a reference, so the newly created struct value has to be boxed.

Jon Skeet
I assumed that. Not entirely sure what made me doubt that would be the case. Just thought I'd throw it out here just in case someone else got the odd wondering.
Sekhat
Give a man a tool to get answers (Red Gate Reflector), and he'll have answers for life. But give him just one answer and he'll be back again with more questions and more SO rep points...
Ben Voigt
@Ben: On the other hand, give a man a tool and they'll have to check it every time they're unsure. Give a man an *explanation* and they'll be able to reason about it for themselves.
Jon Skeet
Note also the slight edge-case covered in my separate reply.
Marc Gravell
@ben dilasm actually did answer the question for me just before Jon answered. Though just throwing the question out there has given extra answers. Marc answer shows how sometime it can ***seem*** your using an interface thats a struct that doesn't get boxed, and it was probably sometime ago I saw that behaviour which might have been what triggered my question.
Sekhat
A: 

The MSDN documentation tells us that structs are value, not reference types. They are boxed when converting to/from a variable of type object. But the central question here is: what about a variable of an interface type? Since the interface can also be implemented by a class, then this must be tantamount to converting from a value to a reference type, as Jon Skeet already said, therefore yes boxing would occur. More discussion on an msdn blog.

sfuqua
+2  A: 

Jon's point is true, but as a side note there is one slight exception to the rule; generics. If you have where T : ISomeInterface, then this is constrained, and uses a special opcode. This means the interface can be used without boxing. For example:

public static void Foo<T>(T obj) where T : ISomeInterface {
    obj.Bar(); // Bar defined on ISomeInterface
}

This does not involve boxing, even for value-type T. However, if (in the same Foo) you do:

ISomeInterface asInterface = obj;
asInterface.Bar();

then that boxes as before. The constrained only applies directly to T.

Marc Gravell
hai, it won't be boxed because the method called after all the generics are resolved is `void Foo(SomeStruct obj)` not `void Foo(ISomeInterface obj)`
Sekhat
@Sekhat: generic parameters are resolved at runtime so the compiler doesn't know the method is called with with a value type.
adrianm
@Sekhat - to expand on @adrianm's point: the same IL is used for all callers. Each value-type param gets JITted separately, but all ref-types share a JIT. The compiler has **nothing** to do with this; .NET generics are runtime, not compile-time. The signature is Foo(T obj) in every case.
Marc Gravell
I never stated whether the generics was resolved at compile time or runtime. However they are resolved at some point. Foo<T> (T obj) is a better fit for SomeStruct than Foo(ISomeInterface interface) because the generic is eventually resolved to mean Foo(SomeStruct obj).
Sekhat
+1  A: 

I'm adding this to hopefully shed a little more light on the answers offered by Jon and Marc.

Consider this non-generic method:

public static void SetToNull(ref ISomeInterface obj) {
    obj = null;
}

Hmm... setting a ref parameter to null. That's only possibly for a reference type, correct? (Well, or for a Nullable<T>; but let's ignore that case to keep things simple.) So the fact that this method compiles tells us that a variable declared to be of some interface type must be treated as a reference type.

The key phrase here is "declared as": consider this attempt to call the above method:

var x = new SomeStruct();

// This line does not compile:
// "Cannot convert from ref SomeStruct to ref ISomeInterface" --
// since x is declared to be of type SomeStruct, it cannot be passed
// to a method that wants a parameter of type ref ISomeInterface.
SetToNull(ref x);

Granted, the reason you can't pass x in the above code to SetToNull is that x would need to be declared as an ISomeInterface for you to be able to pass ref x -- and not because the compiler magically knows that SetToNull includes the line obj = null. But in a way that just reinforces my point: the obj = null line is legal precisely because it would be illegal to pass a variable not declared as an ISomeInterface to the method.

In other words, if a variable is declared as an ISomeInterface, it can be set to null, pure and simple. And that's because interfaces are reference types -- hence, declaring an object as an interface and assigning it to a value type object boxes that value.

Now, on the other hand, consider this hypothetical generic method:

// This method does not compile:
// "Cannot convert null to type parameter 'T' because it could be 
// a non-nullable value type. Consider using 'default(T)' instead." --
// since this method could take a variable declared as, e.g., a SomeStruct,
// the compiler cannot assume a null assignment is legal.
public static void SetToNull<T>(ref T obj) where T : ISomeInterface {
    obj = null;
}
Dan Tao
This has nothing to do with value types and reference types, and everything to do with variance.
Ben Voigt
@Ben: I'm guessing you're saying that because of my `ref` example, which I hesitated to include because I thought it might be a bit confusing. But my point is that if a variable is declared as an `ISomeInterface`, it can be set to null, which is only true of a reference type. Therefore setting an `ISomeInterface` variable to a object of value type incurs boxing. This does have very much to do with value types and reference types. If a variable is declared as a particular value type, that variable *cannot* be set to null.
Dan Tao