The "using" construct looks incredibly handy for situations that require both beginning and separated end parts.
Quick example to illustrate:
using (new Tag("body")) {
Trace.WriteLine("hello!");
}
// ...
class Tag : IDisposable {
String name;
public Tag(String name) {
this.name = name;
Trace.WriteLine("<" + this.name + ">");
Trace.Indent();
}
public void Dispose() {
Trace.Unindent();
Trace.WriteLine("</" + this.name + ">")
}
}
The beginning part is defined as the constructor, the end part is the Dispose method.
However despite of being attractive this construct has a serious caveat that comes from the fact that the Dispose method is called from within a finally block. So there are 2 problems:
You should avoid throwing exceptions from the finally block because they will override the original exception that was supposed to be caught.
There is no way of knowing inside of the Dispose method if an exception was thrown earlier between "beginning" and "end" and thus there is no way of handling the "end" part accordingly.
These 2 things make using of this construct impractical which is a very sad fact. Now, my questions are:
Is my understanding of the problems right? Is this how "using" actually works?
If so, is there any way to overcome these problems and make practical use of the "using" construct other than what it was originally designed for (releasing resources and cleaning up)
In case there is no practical way for "using" to be used this way. What are the alternative approaches (to enforce the context over some code with the beginning and end parts)?
The intent of the using
statement and of the IDisposable
interface is for the user to dispose of unmanaged resources. These resources are usually expensive and precious, so they must be disposed of no matter what (that's why it's on the finally
). Code in finally
blocks can't even be aborted, and it can hang a whole app domain shutdown.
Now, it's very tempting to abuse using
for the purposes you're describing, and I've done that in the past. Most of the time there's no danger there. But if an unexpected exception happens, the whole state of the processing is compromised, you wouldn't necessarily want to run the end operation; so in general, don't do this.
An alternative is to use a lambda, something like this:
public interface IScopable {
void EndScope();
}
public class Tag : IScopable {
private string name;
public Tag(string name) {
this.name = name;
Trace.WriteLine("<" + this.name + ">");
Trace.Indent();
}
public void EndScope() {
Trace.Unindent();
Trace.WriteLine("</" + this.name + ">");
}
}
public static class Scoping {
public static void Scope<T>(this T scopable, Action<T> action)
where T : IScopable {
action(scopable);
scopable.EndScope();
}
}
Use it like this:
new Tag("body").Scope(_ =>
Trace.WriteLine("hello!")
);
You can also create other implementations that run certain actions based on whether exceptions were raised of not.
In Nemerle, the language can be extended with new syntax to support this.