C#’s using
statement is well recognized for one thing: calling dispose on objects so that you don’t have to. That bit is wonderful:
string contents;
using (var f = File.OpenText("/path/to/file") {
contents = f.ReadToEnd();
}
This is much simpler than the fully spelled out alternative:
string contents;
var f = File.OpenText("/path/to/file");
try {
contents = f.ReadToEnd();
} finally {
f.Dispose();
}
And even this longer form actually misses one of the more interesting aspects of the using statement…
string contents;
var f = File.OpenText("/path/to/file");
try {
contents = f.ReadToEnd();
} finally {
f.Dispose();
}
f.ReadToEnd(); // oops! ObjectDisposedException at runtime
f = null; // you could set it to null
f.ReadToEnd(); // but now you have a NullReferenceException, even more mysterious
The using statement on the other hand, creates a scope, so its variable can’t be referenced at all after it is disposed:
string contents;
using (var f = File.OpenText("/path/ro/file") {
contents = f.ReadToEnd();
}
f.ReadToEnd(); // unknown identifier error at compile-time
An entirely equivalent bit of code can be written, using an anonymous scope, but it starts to look quite baroque:
string contents;
{
var f = File.OpenText("/path/ro/file");
try {
contents = f.ReadToEnd();
} finally {
f.Dispose();
}
}
f.ReadToEnd(); // Unknown identifier at compile-time again, but 2x the lines and 2x the scopes!
using provides the try-finally-dispose structure, and also provides a scope. The scope means a whole class of errors where resources are accessed after being released is transformed from run-time to compile-time errors. Dealing with errors at compile-time is quicker, and with the right tools to highlight problems, the compile problems can be seen directly in the code as it is being edited.