Sunday 24 March 2013

C# syntactic sugar - the using statement

The C# using statement (as opposed to the using directive) is usually described along the lines of syntactic sugar for a try/finally block that guarantees to call the Dispose method of a class or struct that implements IDisposable. This is correct except for a subtle detail that I'll explain in a while.

Consider the following code:
using System;

public class Program
{
    public static void Main(string[] args)
    {
        using (var myDisposable = new MyDisposable())
        {
            myDisposable.DoThing();
        }
        // not allowed, myDisposable is out of scope
        //myDisposable.DoThing();
    }
}

public class MyDisposable : IDisposable
{
    public void DoThing()
    {
        // method intentionally left blank
    }

    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }
}
Assuming the standard description of the using statement, this is how lines 7-12 above are expanded by the compiler:
        var myDisposable = new MyDisposable();
        try
        {
            myDisposable.DoThing();
        }
        finally
        {
            if (myDisposable != null)
            {
                ((IDisposable)myDisposable).Dispose();
            }
        }
        // not allowed, myDisposable is out of scope
        //myDisposable.DoThing();
However, the comment at line 13 is no longer correct; the variable myDisposable is now available in the whole method following its declaration.

Variable Scope

Assuming a variable is declared within the using statement, the compiler will scope that variable, by adding a set of braces around its usage. Here's the full method as it is compiled - note the braces at lines 3 and 16:
    public static void Main(string[] args)
    {
        {
            var myDisposable = new MyDisposable();
            try
            {
                myDisposable.DoThing();
            }
            finally
            {
                if (myDisposable != null)
                {
                    ((IDisposable)myDisposable).Dispose();
                }
            }
        }
        // not allowed, myDisposable is out of scope
        //myDisposable.DoThing();
    }
Of course, it is possible to live more dangerously by applying the using statement on a variable declared outside scope of the using statement:
    public static void Main(string[] args)
    {
        var myDisposable = new MyDisposable();
        using (myDisposable)
        {
            myDisposable.DoThing();
        }
        // careful, myDisposable has been disposed
        myDisposable.DoThing();
    }
... if you do that, good luck.

No comments:

Post a Comment