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