Domanda

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:

  1. You should avoid throwing exceptions from the finally block because they will override the original exception that was supposed to be caught.

  2. 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:

  1. Is my understanding of the problems right? Is this how "using" actually works?

  2. 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)

  3. 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)?

È stato utile?

Soluzione

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.

Altri suggerimenti

Your rule #1 applies with or without using, so the rule #2 is the real decider: opt for a try/catch if you must distinguish between situations when an exception has been thrown and the normal program completion.

For example, if your persistence layer may discover issues in the process of using a database connection, your connection needs to close regardless of the fact that there was an exception. In this case, the using construct is a perfect choice.

In some cases you can set up using specifically to detect normal completion vs. an exceptional completion. Ambient transactions provide a perfect example:

using(TransactionScope scope = new TransactionScope()) {
    // Do something that may throw an exception
    scope.Complete();
}

If scope's Dispose is called before Complete has been called, TransactionScope knows that an exception has been thrown, and aborts the transaction.

I dont know if this was the original intention of IDisposable, but Microsoft certanly ARE using it the way you describe (separating begin and end parts). A good example for it is the MVCForm class, provided by the mvc infrastracture. It implements IDisposable and writes the end tag for the form, while i cant see its implementation releasing ant resources (the writer used there to ouput the html seems to stay alive even after the form is disposed).
Alot has been written about the using block and how it "swallows" exceptions (a wcf client is a good sample, you can also find such discussions here, on SO). Personally i also feel alot of times that as much as it is convinient to use the using block, its not really 100% clear when it should and when it should not be used.

Of course, you actually CAN tell within the dispose method if you reached it with our without an error, by adding an extra flag to your class, and raising it within the using block, but this will work only if the person who will use your class will be aware of that flag

You are correct in observing a problem with the design of try/finally blocks, which is in turn a problem with using: there is no clean way for code in a finally block to know whether code execution will continue with the statement following the finally block, or whether there is a pending exception which will be effectively take over as soon as the finally block executes.

I would really like to see a language feature in vb.net and C# which would allow a finally block to include an Exception parameter (e.g.

  try
  {
  }
  finally (Exception ex)
  {
    ...
  }

where the passed-in exception would be null if the try block exited normally, or would hold an exception if it did not. Along with this, I would like to see an IDisposableEx interface, which would inherit Dispose, and include a Dispose(Exception ex) method with the expectation that user code would pass in the ex from the finally block. Any exceptions which occurred during the Dispose could then wrap the passed-in exception (since both the passed-in exception, and the fact that an exception occurred in Dispose, would be relevant).

Failing that, it might be helpful to have .net provide a method which would indicate whether there was an exception pending in the current context. Unfortunately, it's not clear what the exact semantics of such a method should be in various corner cases. By contrast, the semantics of finally (Exception ex) would be perfectly clear. Note, incidentally, that a proper implementation of finally (Exception ex) would require that the language make use of exception filters, but would not require exposing the ability to create arbitrary filters.

In my opinion you are misusing the IDisposable interface. A common practice is to use the interface to release unmanaged resources. Normally, the garbage collector will clean up objects, but in some cases - When yo do NOT longer need the object - you might need to manually clean up.

However in your case, you are not cleaning up an object that is not needed anymore; you are using it to force some logic. You should use another design for that.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top