13

using ” 结构对于需要开始部分和单独结束部分的情况看起来非常方便。

快速示例来说明:

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 + ">")
    }
}

开始部分定义为构造函数,结束部分定义为 Dispose 方法。

然而,尽管这个结构很有吸引力,但有一个严重的警告,那就是 Dispose 方法是从 finally 块中调用的。所以有2个问题:

  1. 您应该避免从 finally 块中抛出异常,因为它们会覆盖应该被捕获的原始异常。

  2. 如果在“开始”和“结束”之间早些时候引发了异常,则无法知道 Dispose 方法的内部,因此无法相应地处理“结束”部分。

这两件事使使用此构造不切实际,这是一个非常可悲的事实。现在,我的问题是:

  1. 我对问题的理解正确吗?这就是“使用”的实际工作方式吗?

  2. 如果是这样,是否有任何方法可以克服这些问题并实际使用“使用”构造,而不是最初设计的用途(释放资源和清理)

  3. 如果没有实际的“使用”方式来使用这种方式。有哪些替代方法(在具有开头和结尾部分的某些代码上强制执行上下文)?

4

5 回答 5

5

您的规则 #1 适用于或不适用using,因此规则 #2 是真正的决定者:如果您必须区分抛出异常和正常程序完成的情况,请选择try/ 。catch

例如,如果您的持久层可能在使用数据库连接的过程中发现问题,那么无论是否出现异常,您的连接都需要关闭。在这种情况下,using构造是一个完美的选择。

在某些情况下,您可以using专门设置来检测正常完成与异常完成。环境事务提供了一个完美的例子:

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

如果scope'sDispose在被调用之前Complete被调用,则TransactionScope知道抛出了异常,并中止事务。

于 2012-06-28T15:02:25.117 回答
2

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.

于 2012-06-29T19:08:39.927 回答
2

我不知道这是否是 的初衷IDisposable,但微软肯定会按照您描述的方式使用它(分离开始和结束部分)。一个很好的例子是 mvc 基础设施提供的 MVCForm 类。它实现IDisposable并编写了表单的结束标记,而我看不到它的实现释放了 ant 资源(用于输出 html 的编写器似乎在表单被处理后仍然存在)。
已经写了很多关于该using块以及它如何“吞下”异常的文章(wcf 客户端是一个很好的示例,您也可以在 SO 上找到此类讨论)。就我个人而言,我也有很多次感觉,尽管使用该using块很方便,但它并没有真正 100% 清楚何时应该使用和何时不应该使用。

当然,您实际上可以在 dispose 方法中判断您是否使用 our 没有错误地到达它,方法是向您的 class 添加一个额外的标志,并在块内引发它using,但这仅适用于将使用您的类的人会知道那个标志

于 2012-06-28T15:26:23.067 回答
1

您观察到 try/finally 块的设计问题是正确的,这反过来又是一个问题using:块中的代码没有干净的方法finally可以知道代码执行是否会继续执行该finally块之后的语句,或者是否有一个挂起的异常将在finally块执行后立即有效地接管。

我真的很想在 vb.net 和 C# 中看到一个允许finally块包含Exception参数的语言功能(例如

  尝试
  {
  }
  最后(例外前)
  {
    ...
  }

null如果块正常退出,则传入的异常将在哪里try,或者如果没有,则将保持异常。除此之外,我希望看到一个IDisposableEx接口,它将继承Dispose,并包含一个Dispose(Exception ex)方法,期望用户代码将在exfromfinally块中传递。然后,在 期间发生的任何异常都Dispose可以包装传入的异常(因为传入的异常和在 中发生异常的事实Dispose都是相关的)。

如果做不到这一点,让 .net 提供一种方法来指示当前上下文中是否存在未决的异常可能会有所帮助。不幸的是,尚不清楚在各种极端情况下这种方法的确切语义应该是什么。相比之下, 的语义finally (Exception ex)将非常清楚。注意,顺便说一句,正确的实现finally (Exception ex)需要语言使用异常过滤器,但不需要公开创建任意过滤器的能力。

于 2012-06-28T15:41:22.013 回答
0

在我看来,您在滥用IDisposable界面。一种常见的做法是使用接口释放非托管资源。通常,垃圾收集器会清理对象,但在某些情况下——当你不再需要对象时——你可能需要手动清理。

但是,在您的情况下,您不会清理不再需要的对象;您正在使用它来强制执行一些逻辑。您应该为此使用另一种设计。

于 2012-06-28T15:01:10.363 回答