Can I safely use object initializers inside using statements?
Asked Answered
C

2

6

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?

Culmination answered 25/7, 2019 at 13:55 Comment(3)
@TimSchmelter I just tested this with the following class: public class Test : IDisposable { public string Property { get => "Hello"; set => throw new Exception();} public void Dispose() { Console.WriteLine("Disposed"); } }. Dispose wasn't called when running using(var x = new Test() { Property = "Test"}).Raid
@JonathonChase Interesting, I think that is because the try-finally, which the compiler translates the using to, checks if the resource is null before calling Dispose() on it, which it is when initialization fails.Culmination
@Culmination Many people have the mental model of the resource variable being set and then properties set on it (which is wrong). That mental model is probably why some people think the object will be disposed.Federica
F
8

The main (only?) danger is that if setting Property fails (i.e. throws an exception), then resource won't be Disposed.

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.

Federica answered 25/7, 2019 at 14:14 Comment(4)
@Culmination By using the object-initializer syntax, the object is first created, then the properties are set. So the instance (that needs to be disposed) is created, but not assigned (as you said) to the variable that will be disposed in the finally block. So you have a leak.Britishism
@RenéVogt Understood. Well, that's disgraceful... By and large, this means that object initializers inside using statements should be considered bad practice.Culmination
@Culmination I am not sure I'd call it disgraceful. It is a side effect (or consequence) of how two syntactic sugar features intersect. It makes perfect sense if you think about it (I mean it might not be what you want - but given how the two features work independently, the way they play together makes sense). Also, in practice it is rarely an issue. Most property setters don't throw exceptions.Federica
@Federica While that is perfectly true, I think C# programmers should be somehow warned of such interactions, since they are almost never a problem, until they unadvertedly become so. Before posting here, I searched for an answer on the official documentation, to no avail.Culmination
S
2

@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.

Songful answered 25/7, 2019 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.