Search code examples
c#structusingidisposable

Is the deep copy of struct type also disposed when in the block of “Using……”?


Suppose I have a struct type implementing IDisposible, and if I use the codes below:

using (MyStruct ms = new MyStruct())
{
     InnerAction(ms);   //Notice "InnerAction" is "InnerAction(MyStruct ms)"
}

Of course I see after the block of using, the ms is disposed. However what about the struct in "InnerAction"? Is it still alive because of deep copy or it is also disposed?

If it's still alive (not disposed), Must I use "ref" for "InnerAction"?

Please give me your proof:)

Thx all.


Solution

  • It's worse than you think: ms is not even disposed.

    The reason is that the using statement makes an internal copy which it calls dispose on in a try/finally construct.

    Consider this LinqPad example:

    void Main()
    {
        MyStruct ms;
        using (ms = new MyStruct())
        {
            InnerAction(ms);
        }
    
        ms.IsDisposed.Dump();
        _naughtyCachedStruct.IsDisposed.Dump();
    }
    
    MyStruct _naughtyCachedStruct;
    
    void InnerAction(MyStruct s)
    {
        _naughtyCachedStruct = s;
    }
    
    struct MyStruct : IDisposable
    {
        public Boolean IsDisposed { get; set; }
    
        public void Dispose()
        {
            IsDisposed = true;
        }
    }
    

    Here's some of the decompiled IL:

    IL_0000:  nop         
    IL_0001:  ldloca.s    01 // CS$0$0000
    IL_0003:  initobj     UserQuery.MyStruct
    IL_0009:  ldloc.1     // CS$0$0000
    IL_000A:  dup         
    IL_000B:  stloc.0     // ms
    IL_000C:  dup         
    IL_000D:  stloc.0     // ms
    IL_000E:  stloc.2     // CS$3$0001
    IL_000F:  nop         
    IL_0010:  ldarg.0     
    IL_0011:  ldloc.0     // ms
    

    Notice that in IL_000E a compiler generated local (CS$3$0001) is created and a copy of ms is stored there. Later...

    IL_001B:  ldloca.s    02 // CS$3$0001
    IL_001D:  constrained. UserQuery.MyStruct
    IL_0023:  callvirt    System.IDisposable.Dispose
    IL_0028:  nop         
    IL_0029:  endfinally  
    

    Dispose is called against this local, not ms (which is stored in location 0).

    The result is that both ms and the copy that InnerAction holds onto are both not disposed.

    Conclusion: don't use structs in using statements.

    EDIT: as @Weston points out in the comments, you can manually box the struct and act on the boxed instance, since it then lives on the heap. This way you can get the instance to dispose, but if you had cast it back to the struct in the using statement, you'll only end up storing a copy before the instance was disposed. Further, boxing removes the benefit of staying off the heap, which you are presumably up to here.

    MyStruct ms = new MyStruct();
    var disposable = (IDisposable)ms;
    using (disposable)
    {
        InnerAction(disposable);
    }
    
    ((MyStruct)disposable).IsDisposed.Dump();