The Using Statement And Disposable Value Types

0 comments suggest edit

A while ago Ian Griffiths wrote about an improvement to his TimedLock class in which he changed it from a class to a struct. This change resulted in a value type that implements IDisposable. I had a nagging question in the back of my mind at the time that I quickly forgot about. The question is wouldn’t instances of that type get boxed when calling Dispose?

So why would I wonder that? Well let’s take a look at some code and go spelunking in IL. The following humble struct is the star of this investigation.

struct MyStruct : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing");
    }
}

Let’s write an application that will instantiate this struct and call its Dispose method via the interface.

public class App
{
    public void DemoDisposable()
    {
        IDisposable disposable = new MyStruct();
        DisoseIt(disposable);
    }
    
    public void DisoseIt(IDisposable disposable)
    {
        disposable.Dispose();
    }
}

Finally we will take our trusty Reflector out and examine the IL (I will leave out the method header).

.maxstack 2
.locals init (
    [0] [mscorlib]System.IDisposable disposable1,
    [1] NeverLockThis.MyStruct struct1)
L_0000: ldloca.s struct1
L_0002: initobj NeverLockThis.MyStruct
L_0008: ldloc.1 
L_0009: box NeverLockThis.MyStruct
L_000e: stloc.0 
L_000f: ldarg.0 
L_0010: ldloc.0 
L_0011: call instance void 
    NeverLockThis.App::DisoseIt([mscorlib]System.IDisposable)
L_0016: ret 

Notice the bolded line has a boxing instruction. As we can see, our struct gets boxed before the Dispose method is called.

The using statement requires that the object provided to it implements IDisposable. Here is a snippet from the MSDN2 docs on the subject.

The using statement allows the programmer to specify when objects that use resources should release them. The object provided to the using statement must implement the IDisposable interface. This interface provides the Dispose method, which should release the object’s resources.

I wondered if the using statement enforced the IDisposable constraint in the same way a method would. Let’s find out. We will add the following new method to the App class.

public void UseMyStruct()
{
    MyStruct structure = new MyStruct();
    using (structure)
    {
        Console.WriteLine(structure.ToString());
    }
}

This code now implicitely calls the Dispose method via the using block. Cracking it open with Reflector reveals…

.maxstack 1
.locals init (
    [0] NeverLockThis.MyStruct struct1,
    [1] NeverLockThis.MyStruct struct2)
L_0000: ldloca.s struct1
L_0002: initobj NeverLockThis.MyStruct
L_0008: ldloc.0 
L_0009: stloc.1 
L_000a: ldloca.s struct1
L_000c: constrained NeverLockThis.MyStruct
L_0012: callvirt instance string object::ToString()
L_0017: call void [mscorlib]System.Console::WriteLine(string)
L_001c: leave.s L_002c
L_001e: ldloca.s struct2
L_0020: constrained NeverLockThis.MyStruct
L_0026: callvirt instance void 
    [mscorlib]System.IDisposable::Dispose()
L_002b: endfinally 
L_002c: ret 
.try L_000a to L_001e finally handler L_001e to L_002c

As you can see, there is no sign of a box statement anywhere to be seen. Forgive me for ever doubting you .NET team. As expected, it does the right thing. I just had to be sure. But do realize that if you pass in a value type that implements IDisposable to a method that takes in IDisposable, a box instruction will occur.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

4 responses

  1. Avatar for Aaron Lerch
    Aaron Lerch August 11th, 2006

    Interesting... if you use Reflector to decompile UseMyStruct into C# it doesn't translate back into a using statement (as it does if a reference type is used), but rather directly into the try/finally that "using" is essentially an alias for. This is confusing to me since both a value and reference type that are wrapped in the using keyword ultimately end up at this IL:
    callvirt instance void [mscorlib]System.IDisposable::Dispose()
    I think the answer lies in this IL statement:
    L_0020: constrained NeverLockThis.MyStruct
    From the documentation for System.Reflection.Emit.OpCodes.Constrained:
    The constrained prefix is designed to allow callvirt instructions to be made in a uniform way independent of whether thisType is a value type or a reference type.
    When a callvirt method instruction has been prefixed by constrained thisType, the instruction is executed as follows:
    * If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.
    * If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.
    * If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.

  2. Avatar for dhananjay singh (dhananjay123@
    dhananjay singh (dhananjay123@ August 13th, 2006

    It is very clear from the implementation of method
    public void DisoseIt(IDisposable disposable)
    {
    disposable.Dispose();
    }
    that variable "disposable" is going to be on heap only, no matter who call it, after all IDisposable is not VALUE type.

  3. Avatar for Haacked
    Haacked August 13th, 2006

    Dhananjay, very true. That is obvious. What wasn't so obivous to me is that the using statement wouldn't do the same. I expected it wouldn't but it wasn't obvious to me.
    I was thinking of the using statement as being similar to a method.
    using(IDisposable obj)
    Since the using statement requires a type of IDisposable, wouldn't it also going to box the paramater passed to it onto the heap? Well I assumed it wouldn't, but dug into it to see why.

  4. Avatar for Nicholas Paldino [.NET/C# MVP]
    Nicholas Paldino [.NET/C# MVP] August 14th, 2006

    It's definitely a nice little thing that the .NET team did here.
    However, you should be aware of one caveat. If you implement IDisposable on your structure explicitly, then it will result in your structure being boxed.
    Juval Lowy has a chapter in his book "Programming .NET Components, 2nd Edition" which describes it in detail (disclosure: I know Juval, and did the technical editing on this book).