繁体   English   中英

“使用”构造和异常处理

[英]“using” construct and exception handling

对于需要开始和分离结束部分的情况,“ 使用 ”构造看起来非常方便。

快速举例说明:

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块中调用的。 所以有两个问题:

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

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

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

  1. 我对问题的理解是对的吗? 这是“使用”实际上如何工作?

  2. 如果是这样,有没有办法克服这些问题,并实际使用“使用”结构,而不是它最初的设计(释放资源和清理)

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

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

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

在某些情况下,您可以设置using专门检测正常完成对一个特殊的完成。 环境交易提供了一个完美的例子:

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

如果在调用Complete之前调用了scopeDispose ,则TransactionScope知道已抛出异常,并中止该事务。

我不知道这是否是IDisposable初衷,但是微软通过你描述的方式使用它(分开开始和结束部分)。 一个很好的例子是MVCForm类,由mvc infrastracture提供。 它实现了IDisposable并为表单编写了结束标记,而我无法看到它的实现释放ant资源(即使在表单处理后,用于输出html的编写器似乎仍然存活)。
Alot已经写过关于using块以及它如何“吞下”异常( wcf客户端是一个很好的样本,你也可以在这里找到这样的讨论)。 就个人而言,我也觉得很多时候使用using块很方便,它应该在它应该和不应该使用时真正100%清楚。

当然,你实际上可以在dispose方法中告诉你,如果你通过我们没有错误到达它,通过在你的类中添加一个额外的标志 ,并在using块中提升它,但这只有当你将使用你的人时才会这样做上课会知道那面旗帜

using语句和IDisposable接口的目的是让用户处置非托管资源。 这些资源通常既昂贵又珍贵,所以无论如何都必须对它们进行处理(这就是它finally的原因)。 finally块中的代码甚至无法中止,它可以挂起整个应用程序域关闭。

现在,为了你所描述的目的滥用using是非常诱人的,而且我过去也是这么做的。 大多数时候那里没有危险 但是,如果发生意外异常,则处理的整个状态会受到影响,您不一定要运行结束操作; 所以一般来说,不要这样做。

另一种方法是使用lambda,如下所示:

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();
  }
}

像这样使用它:

new Tag("body").Scope(_ => 
  Trace.WriteLine("hello!")
);

您还可以根据是否引发异常来创建运行某些操作的其他实现。

在Nemerle中, 可以使用新语法扩展语言以支持此语言。

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

我真的希望在vb.net和C#中看到一个语言特性,它允许finally块包含一个Exception参数(例如

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

如果try块正常退出,则传入的异常将为null ,否则将保留异常。 与此同时,我希望看到一个IDisposableEx接口,它将继承Dispose ,并包含一个Dispose(Exception ex)方法,期望用户代码在finally块中传递ex Dispose期间发生的任何异常都可以包装传入的异常(因为传入的异常以及Dispose发生异常的事实都是相关的)。

如果失败了,那么.net提供一个方法可以指示当前上下文中是否有异常处理。 不幸的是,目前还不清楚这种方法的确切语义应该是在各种角落情况下。 相比之下, finally (Exception ex)的语义将非常清楚。 顺便提一下, finally (Exception ex)的正确实现需要语言使用异常过滤器,但不要求暴露创建任意过滤器的能力。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM