I'm wondering if the use of object initializers inside using statements somehow prevents the correct disposal of the resource declared inside them, e.g.
using (Disposable resource = new Disposable() { Property = property })
{
// ...
}
I've read that object initializers are nothing but synctatic sugar, which the compiler translates to something similar to the following code:
MyClass tmp = new MyClass();
tmp.Property1 = 1;
tmp.Property2 = 2;
actualObjectYouWantToInitialize = tmp;
Even if I may appear as a confused ignorant, I'd like to ask for clarifications. Does the fact that the initialized object is (as far as I understand) a pointer to another object (which is also a pointer, as far as I know), interfere with the disposal of the resource done by a using
statement?
The main (only?) danger is that if setting Property
fails (i.e. throws an exception), then resource
won't be Dispose
d.
using (Disposable resource = new Disposable() { Property = property })
{
// ...
}
Normally exceptions within a using
block are fine - since the using
is syntactic sugar for a try .. finally
.
The issue here is that when Property = property
is executing you effectively aren't yet 'inside' the using
block. This is essentially no different to what happens if the constructor threw an exception.
The thing that the finally
block will be trying to Dispose
is resource
- but resource
was never set - resource
(as shown in your actualObjectYouWantToInitialize
example) is set after the properties have all been set .
https://dotnetfiddle.net/vHeu2F shows how this might occur in practice. Notice that Dispose
was logged once, even though there are two using
blocks.
using System;
namespace Bob
{
public class Disposable : IDisposable
{
private int _property;
public int Property { get => _property; set => throw new Exception(); }
public void Dispose()
{
Console.WriteLine("Disposed");
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("1");
using (var resource = new Disposable())
{
Console.WriteLine("2");
}
Console.WriteLine("3");
using (var resource = new Disposable() { Property = 1 })
{
Console.WriteLine("4");
}
Console.WriteLine("5");
}
}
}
CA2000 may be helpful in detecting this issue.
@mjwills answer is correct. Here are the details:
public void M()
{
using (var x = new Test{Property = ""})
{
}
}
will generate following IL code:
.method public hidebysig
instance void M () cil managed
{
// Method begins at RVA 0x206c
// Code size 35 (0x23)
.maxstack 3
.locals init (
[0] class Test
)
IL_0000: nop
IL_0001: newobj instance void Test::.ctor()
IL_0006: dup
IL_0007: ldstr ""
IL_000c: callvirt instance void Test::set_Property(string)
IL_0011: nop
IL_0012: stloc.0
.try
{
IL_0013: nop
IL_0014: nop
IL_0015: leave.s IL_0022
} // end .try
finally
{
// sequence point: hidden
IL_0017: ldloc.0
IL_0018: brfalse.s IL_0021
IL_001a: ldloc.0
IL_001b: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0020: nop
// sequence point: hidden
IL_0021: endfinally
} // end handler
IL_0022: ret
} // end of method Test::M
You can see that the property setter is called before entering the try which will result in the finally not being called in case of exception in the setter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With