using and IDisposable
The rule "turn off the stove when you leave" doesn't have to live in your head: it can be built into the stove itself. Some stoves have an automatic shut-off: you leave the kitchen, and the stove turns itself off, however you left. In C#, the using statement gives you this kind of automation.
Why is it needed? Some objects hold something that must be released when the work is done: an open file, a network connection. If that is not done, the file stays locked and the connection stays open.
The IDisposable interface
When an object holds a file or a connection, its class implements the IDisposable interface. It has one method, Dispose(): it releases what the object holds.
You can call Dispose() by hand too. But if an exception happens between creating the object and that call, the call is never reached. A call in a finally block runs even after an exception, and using is a short way to write such a block.
Don't confuse the using statement with the using directive from the strings theme. The directive brings a namespace into a file, while the statement calls Dispose() at the end of a block.
Two forms of using
The using statement
The object is created in the parentheses after using, and the work with it goes in the braces:
using (var stove = new Stove()) // Stove on
{
Console.WriteLine("Cooking"); // Cooking
} // Stove off
Console.WriteLine("Done"); // Done
class Stove : IDisposable
{
public Stove() => Console.WriteLine("Stove on");
public void Dispose() => Console.WriteLine("Stove off");
}Let's go through the program step by step:
new Stove()creates a stove, and the constructor printsStove on.using (var stove = …)remembers the object, to call itsDispose()at the end of the block.- The body of the block prints
Cooking. - When execution reaches the closing brace,
usingcallsstove.Dispose(), andStove offis printed. - Then the line after the block runs and prints
Done.
The using var declaration
The short form needs no parentheses and no block of its own: using var stove = new Stove();. Dispose() is called at the end of the block where the declaration stands. This happens even when an exception leaves the block:
try
{
using var stove = new Stove(); // Stove on
Console.WriteLine(int.Parse("x"));
} // Stove off
catch (FormatException)
{
Console.WriteLine("Not a number"); // Not a number
}This fragment continues the first program: put it before the Stove class. Let's go through this example too:
- The declaration creates a stove, and
Stove onis printed. int.Parse("x")throws an exception, and it leaves thetryblock.- As the exception leaves the block,
usingcallsDispose(), andStove offis printed. - Only after that does
catchprintNot a number.
The automatic shut-off analogy falls short in one way. Dispose() is called not when the object is no longer needed but strictly at the end of the block. Until the block ends, the stove is on, even if you are not using it anymore.
using is try and finally
Interviewers like to ask: what does the compiler turn using into? Into code very close to this:
var stove = new Stove(); // Stove on
try
{
Console.WriteLine("Cooking"); // Cooking
}
finally
{
stove.Dispose(); // Stove off
}
class Stove : IDisposable
{
public Stove() => Console.WriteLine("Stove on");
public void Dispose() => Console.WriteLine("Stove off");
}The object is created before try, the body of the block goes into try, and the call to Dispose() into finally. That is why using calls Dispose() on every way out of the block: at its end, after return and after an exception.
Common mistake
An object of a class that does not implement IDisposable was put into using:
using (var box = new Box()) // ✗
{
Console.WriteLine("Inside");
}
class Box
{
public Box() => Console.WriteLine("Box ready");
}error CS1674: 'Box': type used in a using statement must implement 'System.IDisposable'.The program does not compile. using accepts only types that implement IDisposable, and Box does not. A Dispose() method of its own would not help either: the compiler looks for the interface itself. using is only for objects that have something to release; an ordinary object is created without it: var box = new Box();.
Key points
- A class whose objects hold a file or a connection implements
IDisposableand itsDispose()method. using (var x = …) { }andusing var x = …;callDispose()at the end of the block, even if an exception left the block.- The compiler turns
usingintotryandfinally, with the call toDispose()infinally. usingwith an object of a class withoutIDisposabledoes not compile: errorCS1674.