It’s no secret that JetBrains Rider is my IDE of choice. The JetBrains team’s ability to stay current with language additions has helped me keep my C# knowledge relevant in a fast-paced world. It’s always exciting to see refactoring recommendations, and while it’s easy to hit Alt+Enter, I like to understand why I’m about to apply a change.

In this post, we’ll be looking at a relatively new keyword combination of await using and what it means for our codebase.

IDisposable

The IDisposable interface has been around since .NET Framework 1.0 and gives us the ability to release internal resources. Experienced .NET Developers are likely familiar with the disposing pattern.

using (var disposable = new Disposable()) {
  // do your thing here...
}

This interface is fundamental to .NET, with the documentation page for IDisposable listing over 500 implementations of the interface in the framework alone. The count doesn’t even include the use of IDisposable in the open-source ecosystem and commercial products.

There is a flaw with IDisposable, and it’s become more apparent with the introduction of Task. The freeing of resources is synchronous and can block threads. Even worse, incorrect disposable of an asynchronous resource can lead to dead-locks.

IAsyncDisposable

In .NET Core 3.0, we see the introduction of an IAsyncDisposable interface, which is currently derived by a handful of concepts as of writing this post: Microsoft.Extensions.DependencyInjection.ServiceProvider, System.Collections.Generic.IAsyncEnumerator<T>, System.IO.Stream, System.Threading.Timer, and System.Text.Json.Utf8JsonWriter. The interface allows us to free up asynchronous resources by implementing a DisposeAsync method that returns a ValueTask.

public interface IAsyncDisposable
{
  ValueTask DisposeAsync();
}

Note, that IDisposable and IAsyncDisposable are two independent interfaces and can be implemented exclusively of each other. We can imagine that IAsyncDisposable does not implement IDisposable because it does not want to give developers the false impression that there is a synchronous option by default in all cases.

Using IAsyncDisposable In Our Code

Let’s write a simple example that will allow C# to use the await using construct. Our first step is to create a class that implements the IAsyncDisposable interface.

public class HotGarbage : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        Console.WriteLine("What's that smell? *sniff sniff*");
        await Task.Delay(TimeSpan.FromSeconds(3));
        Console.WriteLine("Disposing of Hot Garbage");
    }

    public void Look()
    {
        Console.WriteLine("Hmmm...");
    }
}

Again, we should take note that our class only implements IAsyncDisposable, which means our class likely has dependencies that require asynchronous disposal.

Let’s use our new class.

class Program
{
    static async Task Main(string[] args)
    {
        async Task Run()
        {
            await using var hotGarbage = new HotGarbage();
            hotGarbage.Look();                
        }

        await Run();
    }
}

We are using a local function to create a scope that ends at the end of our code block. The await using keywords are useful as we can reduce the number of braces in our code. Additionally, it reduces the likelihood that a refactor may introduce issues of incorrect disposal.

Running the code, we see the results of our asynchronous disposal.

Hmmm...
What's that smell? *sniff sniff*
Disposing of Hot Garbage

Conclusion

While our IDEs can make great suggestions, it is up to us to understand and implement those suggestions. The IAsyncDisposable interface should make it easier for folks writing libraries that deal with I/O operations. Authors of database access libraries, image processing, and internet protocols should be able to dispose of managed resources gracefully without fear of dead-locks. For us consumers of libraries, we now get some sweet syntactic sugar in the await using keyword, further reducing our codebase’s visual footprint.

What do you think? Do you utilize the await using keywords combo in your code? Why or Why not? Please leave a comment below.