开发者

Is there Boxing/Unboxing when casting a struct into a generic interface? [duplicate]

This question already has answers here: Cl开发者_StackOverflowosed 10 years ago.

Possible Duplicate:

Structs, Interfaces and Boxing

From the MSDN: http://msdn.microsoft.com/en-us/library/yz2be5wk.aspx

Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type.

But what about generic interfaces?

For example, int derives from both IComparable and IComparable<int>.

Let's say I have the following code:

void foo(IComparable value)    { /* etc. */ }
void bar(IComparable<T> value) { /* etc. */ }

void gizmo()
{
   int i = 42;

   bar(i); // is `i` boxed? I'd say YES
   foo(i); // is `i` boxed? I fear it is (but I hope for NO)
}

Does bar (or any function taking a non-generic interface) means there will be boxing?

Does foo (or any function taking a generic interface on the type) means there will be boxing?

Thanks.


Any time a struct is cast to an interface, it is boxed. The purpose of IComparable<T> is to allow for something like:

void bar<T>(T value) where T : IComparable<T> { /* etc. */ }

When used in that fashion, the struct will be passed as a struct (via the generic type parameter) rather than as an interface, and thus will not have to be boxed. Note that depending upon the size of the struct, it may sometimes be better to pass by value and sometimes by reference, though of course if one is using an existing interface like IComparable one must pass as the interface demands.


First, a short (and probably incomplete) primer on value types, reference types, and boxing.

You can tell that something is a value type because changes made in a function do not persist outside the function. The value of the object is copied when the function is called, and thrown away at the end of that function.

You can tell that something is a reference type because changes made in a function persist outside the function. The value of the object is not copied when the function is called, and exists after the end of that function.

If something is boxed, a single copy is made, and seated within a reference type. It effectively changes from a value type to a reference type.

Note that this all applies to instanced state, i.e. any non-static member data. Static members are not instanced state, and have nothing to do with reference types, value types, or boxing. Methods and properties that don't use instanced state (for example, ones that use only local variables or static member data) will not operate differently differently on reference types, value types, or when boxing occurs.

Armed with that knowledge, here is how we can prove that boxing does occur when converting a struct to an interface (generic or not):

using System;

interface ISomeInterface<T>
{
    void Foo();
    T MyValue { get; }
}

struct SomeStruct : ISomeInterface<int>
{
    public void Foo()
    {
        this.myValue++;
    }

    public int MyValue
    {
        get { return myValue; }
    }

    private int myValue;
}

class Program
{
    static void SomeFunction(ISomeInterface<int> value)
    {
        value.Foo();
    }

    static void Main(string[] args)
    {
        SomeStruct test1 = new SomeStruct();
        ISomeInterface<int> test2 = test1;

        // Call with struct directly
        SomeFunction(test1);
        Console.WriteLine(test1.MyValue);
        SomeFunction(test1);
        Console.WriteLine(test1.MyValue);

        // Call with struct converted to interface
        SomeFunction(test2);
        Console.WriteLine(test2.MyValue);
        SomeFunction(test2);
        Console.WriteLine(test2.MyValue);
    }
}

The output looks like this:

0
0
1
2

This means that boxing occurs only when doing the conversion:

  • The first two calls do the boxing upon each call.
  • The second two calls already have a boxed copy, and boxing does not occur upon each call.

I won't bother duplicating all the code here, but if you change ISomeInterface<T> to ISomeInterface, you'll still have the same behavior.


Summary of answers

My confusion about generic interfaces and boxing/unboxing came from the fact I knew C# generics enabled us to produce more efficient code.

For example, the fact int implements IComparable<T> and IComparable meant to me:

  • IComparable was to be used with old, pre-generics code, but would mean boxing/unboxing
  • IComparable<T> was to be used to generics enabled code, supposedly avoiding boxing/unboxing

Eric Lippert's comment is as simple, clear and direct as it can be:

Generic interface types are interface types. There's nothing special about them that magically prevents boxing

From now, I know without doubt that casting a struct into an interface will imply boxing.

But then, how IComparable<T> was supposed to work more efficiently than IComparable?

This is where supercat's answer (edited by Lasse V. Karlsen) pointed me to the fact generics were more like C++ templates than I thought:

The purpose of IComparable is to allow for something like:

   void bar<T>(T value) where T : IComparable<T> { /* etc. */ }

Which is quite different from:

   void bar(IComparable<T> value) { /* etc. */ }

Or even:

   void bar(IComparable value) { /* etc. */ }

My guess is that for the first prototype, the runtime will generate one function per type, and thus, avoid boxing issues when dealing with structs.

Whereas, for the second prototype, the runtime will only generate functions with an interface as a parameter, and as such, do boxing when T is a struct. The third function will just box the struct, no more, no less.

(I guess this is where C# generics combined with C# structs show their superiority when compared with Java type-erasure generics implementation.)

Merlyn Morgan-Graham's answer provided me with an example of test I'll play with at home. I'll complete this summary as soon as I have meaningful results (I guess I'll try to use pass-by-reference semantics to see how all that works...)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜