Today, let's dive into the somewhat unpredictable and potentially risky behavior of the C# 8.0 "Using Declaration" feature. As always, no boring long introduction paragraphs; let's get straight to the point!
In C# 7.0 we did this:
public void SuperMethod()
{
using (var myVariable = new SomeDisposable())
{
// use myVariable
} // myVariable will be disposed here
}
Since C# 8.0, we can write this as follows:
public void SuperMethod()
{
using var myVariable = new SomeDisposable();
// use myVariable
} // myVariable will be disposed here
The lifetime of myVariable
will extend to the end of the scope in which it is declared (in this case — SuperMethod
containing method).
Looks marvelous! But only until we combine it with object initializers...
First, we're going to take a look at this tiny piece of code:
using var response = new HttpResponseMessage
{
Content = new StringContent("Hello, World!")
};
// some other code
At first glance, it looks perfectly fine: we create an instance of a disposable type and initialize one of its properties. When the program execution reaches the end of the method that contains this code, our response
object will be disposed of (the same goes for when "some other code" throws an exception). In other words, we could expect this behavior:
var response = new HttpResponseMessage();
try
{
response.Content = new StringContent("Hello, World!");
// some other code
}
finally
{
response.Dispose();
}
While intuition and common sense might lead us to expect this, the reality is quite different.
To understand what happens, let's check the "Low-Level C#" and IL for the first code snippet that we started with:
Now we can see what is wrong with this lovely Using Declaration construction: if we use an object initializer on our instance, the values will be assigned to the properties before the try-catch block. Thus, if one of these setters throws an exception, we will end up with an allocated instance of a disposable type that is not going to be disposed of by the using
(in the try-catch block).
Frankly, this implementation and design choice caught me off guard.
As you might have guessed, the workaround is straightforward: first, instantiate a disposable object using "Using Declaration," and then initialize its properties — avoid using object initializers.
using var response = new HttpResponseMessage();
response.Content = new StringContent("Hello, World!");
// some other code
If you use an advanced IDE (JetBrains Rider) or an amazing plugin for Visual Studio (Resharper), it will warn you about this code issue:
initialize object properties inside the 'using' statement to ensure that the object is disposed if an exception is thrown during initialization
That's all for now. Thank you for reading; I hope it helps!
Cheers!
Also published here.