Using-declarations in C# 8.0

One of new language features of C# 8.0 is support for using declarations. These declarations enable shorter syntax for declaring disposable variables we want to dispose. Also using declarations give us a little bit cleaner code while compiler makes a dirty work of producing correct code that takes care of disposing disposable variables.

Using declarations in action

Let’s take simple piece of code and see how we can make it use using declarations. Here is one classic block of code where stream and stream reader are used. They both are disposable.

static void MainOld(string[] args)
{
     using (var file = new FileStream("input.txt", FileMode.Open))
     using (var reader = new StreamReader(file))
     {
         var s = reader.ReadToEnd();
 
         // Do something with data
     }
}

Let’s make this code now to using declarations. For this I just make another method to compare them later with disassembler.

static void MainNew(string[] args)
{
     using Stream file = new FileStream("input.txt", FileMode.Open);
     using StreamReader reader = new StreamReader(file);

     var s = reader.ReadToEnd();
 
     // Do something with data
}

We ended up with similar code but it’s a little bit shorter. Where it may pay off are methods with at least three or five using blocks.

Using declarations after compiling

Experienced developers want to ask one question now: what are using declarations internally and how they are handled by compiler? To see what’s going on let’s build the code and open resulting assembly in disassembler. This is what compiler built for us.

private static void MainNew(string[] args)
{
     using (Stream stream = (Stream)new FileStream("input.txt", FileMode.Open))
     {
         using (StreamReader streamReader = new StreamReader(stream))
             streamReader.ReadToEnd();
     }
}
 
private static void MainOld(string[] args)
{
     using (FileStream fileStream = new FileStream("input.txt", FileMode.Open))
     {
         using (StreamReader streamReader = new StreamReader((Stream)fileStream))
             streamReader.ReadToEnd();
     }
}

As a result we got two identical methods. They differ of implementation a bit but what they do is exactly the same.

Scope of using declarations today

Another question that experienced developers ask is how long is automatically generated using block? There are situations where we want using block to be as short as possible to lessen memory or other system resource footprint of application.

Let’s try out with the following piece of code.

static void MainNew(string[] args)
{
    string buffer = null;
    using Stream file = new FileStream("input.txt", FileMode.Open);
    using StreamReader reader = new StreamReader(file);
 
    buffer = reader.ReadToEnd();
 
    // Do something with data
 
    buffer = null;
}
 
static void MainOld(string[] args)
{
    string buffer = null;
 
    using (var file = new FileStream("input.txt", FileMode.Open))
    using (var reader = new StreamReader(file))
    {
        buffer = reader.ReadToEnd();
    }
  
    // Do something with data
  
    buffer = null;
}

MainOld() method keeps disposables up only until the contents of file are read. After this file stream and stream reader are disposed immediately. For MainNew() method we don’t know what happens and it’s not possible for us to read it out from code. Let’s build again and use disassembler again to see what compiler built.

private static void MainNew(string[] args)
{
    string str = (string)null;
    using (Stream stream = (Stream)new FileStream("input.txt", FileMode.Open))
    {
        using (StreamReader streamReader = new StreamReader(stream))
        {
            str = streamReader.ReadToEnd();
            str = (string)null;
        }
    }
}
 
private static void MainOld(string[] args)
{
    string str = (string)null;
    using (FileStream fileStream = new FileStream("input.txt", FileMode.Open))
    {
        using (StreamReader streamReader = new StreamReader((Stream)fileStream))
            str = streamReader.ReadToEnd();
    }
    str = (string)null;
}

We got again similar methods but they are not same. Take a look at where using blocks end in both methods. In MainOld() method disposables are kept alive for sorter time than in MainNew() method.

Warning! C# 8.0 is not stable yet and things may change. Until there is no stable version yet we cannot say if this difference will remain or if it can be controlled somehow. We can be sure that the difference exists today and when writing C# 8 code today we have to consider length of scope of using declarations blocks.

Wrapping up

Using declarations help us write a little bit shorter code where details of using blocks are hidden from us. In current implementation using block seems to cover all code from point where disposable variable was declared using using declaration. If this holds true for first stable release and from there on – I don’t know. We can all be part of the story and tell what we think at official C# language discussions. Personally I prefer shortest scopes possible.

Liked this post? Empower your friends by sharing it!

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    Leave a Reply

    Your email address will not be published. Required fields are marked *