[英]“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块中调用的。 所以有两个问题:
您应该避免从finally块中抛出异常,因为它们将覆盖应该捕获的原始异常。
如果在“开始”和“结束”之间抛出异常,则无法知道Dispose方法的内部,因此无法相应地处理“结束”部分。
这两件事使得使用这种结构变得不切实际,这是一个非常可悲的事实。 现在,我的问题是:
我对问题的理解是对的吗? 这是“使用”实际上如何工作?
如果是这样,有没有办法克服这些问题,并实际使用“使用”结构,而不是它最初的设计(释放资源和清理)
如果没有实用的方法来“使用”这种方式。 有哪些替代方法(使用开头和结尾部分强制执行某些代码的上下文)?
您的规则#1适用于或不using
,因此规则#2是真正的决策者:如果您必须区分抛出异常的情况和正常的程序完成,请选择try
/ catch
。
例如,如果持久层可能在使用数据库连接的过程中发现问题,则无论是否存在异常,您的连接都需要关闭。 在这种情况下, using
构造是一个完美的选择。
在某些情况下,您可以设置using
专门检测正常完成对一个特殊的完成。 环境交易提供了一个完美的例子:
using(TransactionScope scope = new TransactionScope()) {
// Do something that may throw an exception
scope.Complete();
}
如果在调用Complete
之前调用了scope
的Dispose
,则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.