Beware of C# 8 Using Statements

Let’s be honest here, C# 8 is crazy cool. It has such a big set of new features to make developers more productive and write even better code. There are some awesome things, like nullable reference types and asynchronous streams (async enumerables). There are also some weird things, like private members on interfaces. But there are many features that just make life better. Using statements is one of them.

If you want to see a list of all the new things, check out the Microsoft docs. It has all the goodness.

But, let’s get back to the using statements. What are they? Where are they used? Well, using statements are just normal using blocks with some syntactic sugar to make the code more readable. Here is an example of a normal, traditional using block:

using (var stream = File.Create("test.txt"))
using (var writer = new StreamWriter(stream))
{
    writer.Write("Hello World!");
}

This is a very simple example. There are two objects that need to be disposed, and we have them in the using blocks. This code creates a file, creates a writer and then writes to the file. At the end, the writer closes the stream and the file is closed. We have all seen this before.

So, what does C# 8 bring to the table? Well, lets convert the using block into a using statement:

using var stream = File.Create("test.txt");
using var writer = new StreamWriter(stream);

writer.Write("Hello World!");

It is very similar, but there are no braces and parenthesis. There are no indents.

The Good

That was a simple example, but just imagine the case where there are loops, conditions and other statements. It could, and does, get a bit messy. Take a look at this example:

var array = new[] { "first", "second", "third" };
foreach (var item in array)
{
    using (var stream = File.Create($"{item}.txt"))
    {
        stream.WriteByte(1);
        if (item != null)
        {
            using (var writer = new StreamWriter(stream))
            {
                writer.Write("Hello World!");
            }
        }
    }
}

Lots of nesting, lots of indents, lots of braces. With using statements we can remove 4 lines and 2 levels of indents. Much better on the eyes:

var array = new[] { "first", "second", "third" };
foreach (var item in array)
{
    using var stream = File.Create($"{item}.txt");
    stream.WriteByte(1);
    if (item != null)
    {
        using var writer = new StreamWriter(stream);
        writer.Write("Hello World!");
    }
}

The Bad

We saw a nice saving of everything, so what is this “beware” in the title? Well, it has to do with scopes. The using statements are nice as they dispose/close things when they go out of scope.

If we look at the example above, we can see the stream variable goes out of scope at the end of each iteration of the foreach loop. This means that after the code in the foreach block runs, it is auto disposed. That is nice. But, this is also the cause of the “beware”. Take a look at this example:

using (var stream = File.Create("test.txt"))
using (var writer = new StreamWriter(stream))
{
    writer.Write("Hello World!");
}

return File.ReadAllText("test.txt");

Here, we create a file, write to it, dispose it and then return the results. We probably wouldn’t write this exact code in real life, but hey, examples! If we were to convert this into using statements, as one would be tempted, we end up with this code:

using var stream = File.Create("test.txt");
using var writer = new StreamWriter(stream);

writer.Write("Hello World!");

return File.ReadAllText("test.txt");

That looks nice, but has a “hidden” bug. If you think back to the using statements and their scope… Where does the stream go out of scope? At the end of the method, after the return. So what does this mean? Well, it is that the stream is still open and the file handle is being held. That means we will get an exception:

Unhandled exception. System.IO.IOException:
The process cannot access the file ‘test.txt’ because it is being used by another process.

Not so nice.

We can have a look at what the compiler writes to help us understand what just happened:

using (var stream = File.Create("test.txt"))
using (var writer = new StreamWriter(stream))
{
    writer.Write("Hello World!");

    return File.ReadAllText("test.txt");
}

As you can see, the return is inside the using block.

Not what we wanted.

The Ugly

Just when we thought we had the bad news, there is more. An exception is not nice, but at least we know something went wrong. We can fix that. So what is worse than an unhandled exception? No exception!

If our operating system was cool with opening a file that was already opened. We cannot be sure that the contents had actually been flushed! Some streams only flush when the stream is either explicitly flushed or when it is closed.

So, if we are running on this system, we would not get an exception, but we would return an empty string. Even though we just wrote to the file! This is most certainly an unexpected result!

Now, streams are one thing and we can handle that. But it gets tricky when we have other disposable objects.

Maybe they are very large. If we have 1GB of RAM and we open a 1GB file, we are good. But if we try an open another 1GB file, we will run out of memory. If we were using blocks, then we could control the fact that it had to first close the first file before opening the second.

Maybe they are critical. We could potentially lock some resource for longer than necessary if we aren’t careful. This is very dangerous. We could potentially cause other systems of functions to crash because things took too long to respond.

At the end of the day. Use the new using statements. Your code will be better. But… Use them were they should be used. Don’t just auto-convert all blocks to statements with ReSharper.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s