Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I safely use object initializers inside using statements?

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?

like image 657
StackLloyd Avatar asked Jan 26 '23 00:01

StackLloyd


2 Answers

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.

like image 197
mjwills Avatar answered Jan 28 '23 13:01

mjwills


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

like image 41
Bruno Belmondo Avatar answered Jan 28 '23 15:01

Bruno Belmondo