36

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

Samuel Neff
  • 73,278
  • 17
  • 138
  • 182
john green
  • 465
  • 5
  • 5
  • 7
    Why do you have a struct that implements IDisposable? – Yuriy Faktorovich Mar 09 '10 at 22:15
  • 5
    That sounds awefully dangerous. With its copy on assignment semantics, it is very, very hard to tell how many copies of a struct you have. – Jonathan Allen Mar 09 '10 at 22:18
  • 2
    @Jonathan Allen - The number of copies of the struct is not really important, though, in the context of a using statement. The important thing is that the unmanaged resource that is pointed at by the struct is disposed of properly. That reference should not be difficult to track in copy-on-assignment semantics. – Jeffrey L Whitledge Mar 09 '10 at 22:21
  • 1
    What I fear is that someone will misuse your struct and cause the resource to be double-freed. Depending on what that resource is, you could have anything from an exception to all out memory corruption. – Jonathan Allen Mar 09 '10 at 22:30
  • 1
    I cannot think of a good reason to implement IDisposable in a struct. What is stopping you from using a class? – Ed S. Mar 10 '10 at 00:21

4 Answers4

39

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.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Ta01
  • 31,040
  • 13
  • 70
  • 99
  • But `using` does not just call Dispose(), it also stores it as an interface reference. – H H Mar 09 '10 at 22:22
  • @Henk Holterman, are you sure it's an interface reference? When you say `using (SQLConnection = new SQLConnection())` we've declared the full type. There's no interface in there and no reason to assume `using` is going to add one. – Samuel Neff Mar 09 '10 at 22:29
  • Sam, see the official compiler-rewrite: `using(x) {...}` -> `try {...} finally { if (x != null) x.Dispose(); }`. – H H Mar 09 '10 at 22:32
  • 1
    @Henk Holterman - The expansion that you are giving is only for reference types. There is a separate expansion for value types. – Jeffrey L Whitledge Mar 09 '10 at 22:36
  • 13
    +1 I just created a test application to see what happens, and looked at it in ildasm. There is no boxing going on. It is exactly as this answer indicates. – Jeffrey L Whitledge Mar 09 '10 at 22:37
  • @Henk Holterman, and where in your compiler rewrite does it change the declaration to use `IDisposable`? It doesn't. – Samuel Neff Mar 09 '10 at 22:44
30

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.
Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 21
    The most obvious reason you wouldn't want it to be boxed is for performance reasons (e.g. you want to simulate C++ RAII, which is low-cost, in C#). – Qwertie Dec 20 '11 at 16:06
  • “Since most of the time this is what you want anyway” — In what situations is it *not* what I want anyway? Apart from slightly different memory-usage characteristics, I can’t think of any observable difference? – Timwi Apr 27 '12 at 11:36
  • @Timwi If this were C#, you might want to call a private interface implementation and not the public non-interface method of the same name, for instance. Of course, the IL actually does call the interface method but without the boxing C# would require. Another observable difference is that Dispose quite possibly mutates internal state - if you want to observe that mutation, then you need access to the disposed object, not the source for its copy. – Eamon Nerbonne Jun 16 '14 at 10:12
  • I think if struct is readonly and is disposing on a reference type it should be safe. if struct implements Dispose explicitly, it will get boxed. – M.kazem Akhgary Jan 27 '19 at 13:13
  • 7
    I am applying the `using(new SomeScopedBehavior())` idiom to guarantee my End method gets called for every Begin method, even where I have multiple returns or an exception. I'm also using Unity, so I am trying to avoid creating 3+kb of heap trash every second just for this one call. Would you argue that I should be doing `try { ... } finally { ... }` instead for clarity, or would you say the `using` idiom with a struct is the better thing to do here? My `IDisposable` object just wraps static methods, so it's no skin off my nose. – Merlyn Morgan-Graham May 03 '19 at 02:21
  • @MerlynMorgan-Graham I found this question because of Unity too. It seems `using(new SomeScopedBehavior())` is good idiom. Unity guys do the same in their engine, for example there is a struct `EditorGUI.DisabledScope` which is used in the inspector draw loop to draw disabled controls inside `using`. – dmitry1100 Jul 07 '21 at 09:10
17

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.

Samuel Neff
  • 73,278
  • 17
  • 138
  • 182
8

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.

JMarsch
  • 21,484
  • 15
  • 77
  • 125
  • I found the definition for the "constrained" keyword. Definitely no boxing -- it's just like bnkdev said: http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained.aspx – JMarsch Mar 09 '10 at 23:27
  • + 1 Excellent! That's certainly one way to make sure. – Patrick Karcher Mar 09 '10 at 23:57