18

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.

Community
  • 1
  • 1
paercebal
  • 81,378
  • 38
  • 130
  • 159

3 Answers3

23

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.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
supercat
  • 77,689
  • 9
  • 166
  • 211
  • 2
    Everything after "Any time a struct is cast to an interface, it is boxed" is somewhat out-of-topic . . . but in the same time, it made me realize something important about generics, structs and interface, something a straight complete answer wouldn't have, namely, the difference between `bar(IComparable value)` and `bar(T value) where T : IComparable`, and so, why interfaces still matter a lot for structs, as long as we uses generics correctly. +1. – paercebal Apr 24 '11 at 09:23
  • 1
    @paercebal: To my mind, what matters is not whether an answer precisely answers the exact question asked, but whether it provides information that will be useful to the original asker, and other people who click on the question. I figured that you would not only want to know whether a particular syntax would cause boxing, but also whether there would be an alternative way to write the code that would not. BTW, if I want to find out whether something will cause boxing, I write a simple program to create a WeakReference pointing to a new object (which is otherwise unused), then... – supercat Apr 24 '11 at 19:37
  • ...I run the operation that might or might not require boxing a million times, and then see if the WeakReference is still valid. If the operation required any sort of heap allocation, a garbage-collection will occur and the WeakReference would be voided. If not, it won't. Note that there may be even better ways to detect garbage collection, but this way is pretty quick and easy to test. – supercat Apr 24 '11 at 19:39
8

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.

Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
  • 1
    I don't think I agree with your characterization of value types vs reference types. Suppose you have a static field struct C { public static int x; } and a method F() { C.x = 123; }. The change to C.x persists outside of the call to F, but neither C nor C.x are reference types. – Eric Lippert Apr 22 '11 at 18:14
  • @Eric: Good point :) It is true that my description only applies to instanced state. I will modify the answer to try to make this clearer. Or do you think I'm over-simplifying the issue? – Merlyn Morgan-Graham Apr 22 '11 at 19:10
  • @Eric Lippert : Right, but in the case you describe, the static field is just a glorified global variable hidden within a struct, and so, not really part of the boxing/not-boxing problem. Am I wrong? – paercebal Apr 24 '11 at 09:27
  • @Merlyn Morgan-Graham : Is it important for this demonstration to have the method declared `ISomeInterface.Foo` instead of `Foo`? – paercebal Apr 24 '11 at 09:30
  • @Merlyn Morgan-Graham : +1. Thanks for the test. While I knew about the reference thing, it just not occurred to me I could use this to indirectly detect boxing/unboxing. I'm playing with that right now... :-) – paercebal Apr 24 '11 at 09:45
  • @paercebal: I think I got distracted in the middle of writing the sample code. That syntax is called implicit declaration, and is useful if you don't want to dirty up the public interface of the concrete class with interface implementations, or if you need a different implementation of a method for different interfaces. When using it, you have to cast to the interface type in order to use those methods. For some reason I thought it was necessary. It actually isn't, which I am glad about :) I've fixed the sample code. – Merlyn Morgan-Graham Apr 24 '11 at 20:25
  • Some people insist that a boxed value type is really a value type rather than a reference-type instance. I agree with your philosophy. Unfortunately, I don't know any decent way to make boxed value type instances implement `Equals` as reference equality. I have a kludge, but it's nasty. – supercat Sep 26 '12 at 21:21
7

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...)

Community
  • 1
  • 1
paercebal
  • 81,378
  • 38
  • 130
  • 159