【问题标题】:Try/Finally block vs calling dispose?尝试/最终阻止与调用处置?
【发布时间】:2023-08-29 02:56:02
【问题描述】:

这两个代码示例有什么区别吗?如果没有,为什么using 存在?

StreamWriter writer;
try {
    writer = new StreamWriter(...)
    writer.blahblah();

} finally {
    writer.Dispose();
}

对比:

using (Streamwriter writer = new Streamwriter(...)) {
    writer.blahblah
}

我的意思是,在第二个示例中,您确实应该将它放在 try 块中,因此添加 finally 块确实不会花费更多的精力。我知道整个事情可能包含在一个更大的 try 块中,但是是的,这对我来说似乎是多余的。

【问题讨论】:

  • “你真的应该把它放在 try 块中” 为什么?您可以允许任何异常冒泡到堆栈顶部,并在日志中留下清晰的消息和堆栈跟踪。
  • foreach 也是如此,它只是调用迭代器方法和属性的更简洁的版本。
  • 您的 try/finally 样本有缺陷,using(){} 块将正常工作。
  • 我一直听说在 Dispose 方法执行期间抛出异常时,Using 语句似乎与Try-Finally 块相比没有用。我还看到 this MSDN 文章对 WCF 客户端说同样的话。

标签: c# try-catch dispose using streamwriter


【解决方案1】:

您的代码存在一些问题。请永远不要这样写(改为使用),这就是原因

StreamWriter writer;
try {
    // What if you failed here to create StreamWriter? 
    // E.g. you haven't got permissions, the path is wrong etc. 
    // In this case "writer" will point to trash and
    // The "finally" section will be executed
    writer = new StreamWriter(...) 
    writer.blahblah();
} finally {
    // If you failed to execute the StreamWriter's constructor
    // "writer" points to trash and you'll probably crash with Access Violation
    // Moreover, this Access Violation will be an unstable error!
    writer.Dispose(); 
}

当你这样输入using

  using (StreamWriter writer = new StreamWriter(...)) {
    writer.blahblah();
  }

等于代码

StreamWriter writer = null; // <- note the assignment

try {
  writer = new StreamWriter(...); 
  writer.blahblah();
}
finally {
  if (!Object.ReferenceEquals(null, writer)) // <- ... And to the check
    writer.Dispose();
}

【讨论】:

  • 为什么使用 Object.ReferenceEquals(null) 而不仅仅是 !=null ?这里回答:*.com/a/51795257/2377343
  • @T.Todua 为什么是!=null 而不仅仅是writer?.Dispose()?我的问题的答案和你的一样:语言在进化。尤其是德米特里写下他的答案已经超过 6.5 年了 ;-)
【解决方案2】:

这两个代码示例有什么区别

是的,using 在调用 Dispose 之前检查 null(即,它被扩展为引入空检查的实际代码)。

为什么使用存在?

因为代码更简洁。只是一个语法糖。

【讨论】:

    【解决方案3】:

    你所写的几乎就是using 总结的模式。因此,using 的意义在于避免每次使用 Disposable 对象时都必须编写相同的 try....finally 块。

    至于您编辑的问题

    [...] 在第二个示例中,您确实应该将其放在 try 块中,因此添加 finally 块确实不会花费更多的精力

    您可能无法(或不想)显式处理来自blahblah 的错误,而您只是希望它冒泡到调用代码......但仍然在清理您的 StreamWriter 资源!

    所以你最终会得到这个:

    StreamWriter writer;
    try{
        writer = new StreamWriter(...)
        writer.blahblah();
    }catch{
      throw; // pointless!
    } finally [
        writer.Dispose();
    }
    

    【讨论】:

    • 所以你最终会得到这个 --> 你为什么要那样做? finally 不会吞下异常,因此如果您只想将异常冒泡到调用代码,则根本不需要显式抛出它。
    • OP 说他们无论如何都会放一个try..catch,这只是一个人为的例子,说明为什么你可能不需要。
    • 如果catch 块记录了发生的异常,并且finally 块根据原始文件是否正确处理Dispose 相关问题,则后一种模式可能没有用。 try 块已正常退出或通过异常退出。
    【解决方案4】:

    后者只是前者的语法糖。他们应该做同样的事情,但后者需要更少的样板代码。

    我建议使用using 一个,因为它不太可能出错。

    【讨论】:

    • 不幸的是,后者不仅仅是前者的语法糖。有一个微妙的区别:如果在前者中,StreamWriter 构造函数中抛出异常,代码将因浮动 AccessViolation 而崩溃,而稍后(即“使用{...}”)将正确运行
    【解决方案5】:

    它们并不完全相同。 try/finally 块不能防止愚蠢的错误。

    StreamWriter writer = new StreamWriter(...);
    try {
      ...
      writer = new StreamWriter(...);
      ...
    } finally {
      writer.Dispose();
    }
    

    请注意,只有第二个写入器会被处理掉。相比之下,

    using (StreamWriter writer = new StreamWriter(...)) {
      ...
      writer = new StreamWriter(...);
      ...
    }
    

    将给出编译时错误。

    【讨论】:

      【解决方案6】:

      首先,使用using 比您的代码更安全 - 它可以正确处理Disposable 对象构造中的错误,并且不会对空对象调用 dispose。

      第二个区别在于代码的可读性 - 看看你的例子。第一个版本需要 7 行。第二个 - 只有 3 个。

      【讨论】:

      • 不幸的是,真正的区别不仅仅在于代码的可读性。如果 StreamWriter 构造函数中抛出异常,“try {} catch {}”将因浮动访问冲突而崩溃;而“使用 {}”会正确执行。
      • 这就是第一段的内容。但我会重写它,因为它显然不清楚。