If my struct implements IDisposable will it be boxed when used in a using statement?

If my struct implements IDisposable will it be boxed when used in a using statement?

Thanks

Edit: this timedlock is a struct and implements Idisposable. http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking

Edit 2: Looking at the IL it seems If your struct exposes Dispose() as public, the compiler calls Dispose when an instance of the struct goes out of scope if you forget to call Dispose() (for example, you are not using the "using" statement)?


Solution 1:

Per Eric Lippert:

A call to IDisposable.Dispose on a struct is generated as a constrained virtual call, which most of the time does NOT box the value.

A constrained virtual call on a value type only boxes the value if the virtual method is NOT implemented by the type. The only circumstances under which a virtual method can be unimplemented by the value type is when the method is, say, ToString, and implemented by the base class, System.ValueType.

See section 2.1 of Partition III of the CLI documentation for more detail.

Solution 2:

This is a duplicate of When does a using-statement box its argument, when it's a struct?

UPDATE: This question was the subject of my blog in March of 2011. Thanks for the great question.

A few points:

  • As others have correctly pointed out, a value type that implements IDisposable is not boxed when it is disposed as a consequence of control leaving a using statement.
  • This is technically a violation of the C# specification. The spec states that the finally block should have the semantics of ((IDisposable)resource).Dispose(); which is clearly a boxing conversion. We do not actually generate the boxing conversion. Since most of the time this is what you want anyway, we're not losing any sleep over it.
  • A disposable value type seems like a potentially bad idea. It's too easy to accidentally make a copy of a value type; they are copied by value after all.
  • Why on earth do you care whether this boxes or not? I hope you're not asking this because you want the dispose method to mutate the variable containing the value type. That would be a bad idea indeed. Mutable value types are evil.

Solution 3:

No, it does not get boxed.

using is not a method call. It's syntactic sugar that the compiler just converts into, roughtly, this:

MyClass m = new MyClass()
try
{
    // ...
}
finally
{
    if (m != null) {
        m.Dispose();
    }
}

It never uses IDisposable in the declaration and never passes the instance to anything else. For a struct, the compiler actually generates something even smaller:

MyStruct m = new MyStruct()
try
{
    // ...
}
finally
{
    m.Dispose();
}

Since a struct can't be null.

Now, to be 100% sure it never boxes, look at the IL.

Try this sample code:

class StructBox
{
    public static void Test()
    {
        using(MyStruct m = new MyStruct())
        {

        }


        MyStruct m2 = new MyStruct();
        DisposeSomething(m2);
    }

    public static void DisposeSomething(IDisposable disposable)
    {
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    private struct MyStruct : IDisposable
    {           
        public void Dispose()
        {
            // just kidding
        }
    }
}

Then look at the IL:

.method public hidebysig static void Test() cil managed
{
    .maxstack 1
    .locals init (
        [0] valuetype ConsoleApplication1.StructBox/MyStruct m,
        [1] valuetype ConsoleApplication1.StructBox/MyStruct m2)
    L_0000: ldloca.s m
    L_0002: initobj ConsoleApplication1.StructBox/MyStruct
    L_0008: leave.s L_0018
    L_000a: ldloca.s m
    L_000c: constrained ConsoleApplication1.StructBox/MyStruct
    L_0012: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0017: endfinally 
    L_0018: ldloca.s m2
    L_001a: initobj ConsoleApplication1.StructBox/MyStruct
    L_0020: ldloc.1 
    L_0021: box ConsoleApplication1.StructBox/MyStruct
    L_0026: call void ConsoleApplication1.StructBox::DisposeSomething(class [mscorlib]System.IDisposable)
    L_002b: ret 
    .try L_0008 to L_000a finally handler L_000a to L_0018
}

Lines L_0000 through L_0017 represent the m declaration and using. There is no boxing.

Lines L_0018 through L_0026 represent the m2 declaration and call to DisposeSomething. See on line L_0021 box.

Solution 4:

This will not box (surprised me). I think that bnkdev's explanation covers it. Here's how I proofed it:

Wrote the quick console app below (note, I included BoxTest(), which I know will box, so that I had something to compare to).

Then I used Reflector to disassemble the compiled output to IL (you could use ILDASM).


namespace StructInterfaceBoxingTest
{
    public struct TestStruct : IDisposable
    {
        #region IDisposable Members

        public void Dispose()
        {
            System.Console.WriteLine("Boo!");
        }

        #endregion
    }


    class Program
    {
        static void Main(string[] args)
        {
            using (TestStruct str = new TestStruct())
            {

            }
        }

        static void BoxTest()
        {
            TestStruct str = new TestStruct();
            ThisWillBox(str);
        }

        static void ThisWillBox(object item) {}
    }
}

Ok, so first, here's the IL for BoxTest -- note the box instruction on line L_000a (astersik emphasis mine)


.method private hidebysig static void BoxTest() cil managed
{
    .maxstack 1
    .locals init (
        [0] valuetype StructInterfaceBoxingTest.TestStruct str)
    L_0000: nop 
    L_0001: ldloca.s str
    L_0003: initobj StructInterfaceBoxingTest.TestStruct
    L_0009: ldloc.0 
    L_000a: **box** StructInterfaceBoxingTest.TestStruct
    L_000f: call void StructInterfaceBoxingTest.Program::ThisWillBox(object)
    L_0014: nop 
    L_0015: ret 
}

Now have a look at Main (where we use a using statement with our IDisposable struct):


.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype StructInterfaceBoxingTest.TestStruct str)
    L_0000: nop 
    L_0001: ldloca.s str
    L_0003: initobj StructInterfaceBoxingTest.TestStruct
    L_0009: nop 
    L_000a: nop 
    L_000b: leave.s L_001c
    L_000d: ldloca.s str
    L_000f: constrained StructInterfaceBoxingTest.TestStruct
    L_0015: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_001a: nop 
    L_001b: endfinally 
    L_001c: nop 
    L_001d: ret 
    .try L_0009 to L_000d finally handler L_000d to L_001c
}

Note the constrained keyword on line L_000f. I can't find a reference for exactly what that keyword means, but I if you read bnkdev's post, I think that this is the constrained virual call tha the is describing.