【问题标题】:Using statement and try-catch()-finally repetition?使用语句和 try-catch()-finally 重复?
【发布时间】:2010-11-25 02:14:05
【问题描述】:

using(...) 语句是 try{} finally {} 的语法糖。

但是如果我有如下的 using 语句:

using (FileStream fs = File.Open(path))
{


}

现在我想捕获打开此文件可能导致的异常(这是相当高风险的代码,因为它可能由于环境而失败),但是如果我在里面写 try-catch 会不会是重复?当代码编译到 IL 时,我假设代码被 JITted 时重复将被删除?

但是,我想捕获打开文件可能导致的异常(因此我应该将 try-catch 包装在 using 语句的范围之外),以及我在 using 块内执行的任何操作的异常,因此我应该添加块内的try-catch。

这似乎是我在 CLR 内部可能执行的操作中添加了很多重复内容。 CLR 是否添加了 catch 子句?

我的同事认为 using 语句很混乱(但这是因为我对它们进行了硬编码,因为我需要非常快速地更改它们并且无法访问代码库的其他部分,因此单行稍长)。说同事不使用 using 语句,但是 using 语句和 try-finally/try-catch-finally 之间是否存在任何功能差异?我确实看到了一个案例,其中 WCF 服务有一个不为人知的关于使用 finally 和返回值(关于 finally)的极端案例。解决方案是使用检查块。 C#中有这样的东西吗?

另一方面,所有实现 IDisposale 的类型都是非托管资源的所有者吗?与我朋友的讨论指出答案是否定的。 (我也在这个论坛的使用部分阅读了一些帖子,那里有一些非常好的知识)。

【问题讨论】:

    标签: c# using try-catch-finally


    【解决方案1】:

    如果您确实需要处理一些异常,您可以自己实现该模式。就个人而言,我仍然发现将 using 包装在 try/catch 块中更简单(更重要的是,更清晰)。

    但是,如果您自己做,请确保您做对了。 Using 块还创建了一个匿名范围块,以便您的变量更快地符合收集条件。在Using 块末尾调用的.Dispose() 方法仅清理非托管 资源,因此您的对象持有的任何内存可能会停留更长时间。这不太可能是一个大问题,但值得记住以防万一。

    因此,要直接适应模式,您的代码需要看起来更像这样:

    {
        FileStream fs;
        try
        {
            fs = File.Open(path);
    
        }
        catch (FileNotFoundException e) { /* ... */ }
        catch (IOException e) { /* ... */ }
        catch (Exception e) {/* ... */}
        finally
        {
            if (fs != null) fs.Dispose();
        }
    }
    

    就个人而言,我希望看到 Using 扩展为支持 CatchFinally 块。由于他们已经对代码执行了转换,因此这似乎不会增加太多额外的复杂性。

    【讨论】:

    • 虽然最终使用支持吗?您是从哪里了解到使用匿名范围块的?我想了解更多关于这方面的信息。因此,当我在 using 块(例如 FileSream.Open())中打开文件时,此异常会冒泡。如果 using 语句实现了 try/finally,我必须将其包装在 try/catch 中才能捕获。
    【解决方案2】:

    我的偏好是保留 using 语句并将其包装在 try/catch 中。外部 try/catch 将捕获您需要注意的任何异常,同时忽略 Dispose()。如果您稍后将其重构以将 try/catch 移动到其他位置(例如在调用函数中),这具有额外的优势,可以保护您免受自己的伤害。

    就您关于 IDisposable 的问题而言:任何人都可以出于他们喜欢的任何原因实现此功能。没有技术理由将其限制为非托管资源。 (是否应该限制在你的代码中的非托管资源是一个不同的问题。

    【讨论】:

      【解决方案3】:

      using 和 try-finally 最大的区别在于 using 只会调用Dispose() 方法。如果你实现自己的 finally 块可以执行其他逻辑,例如日志记录,这些不会包含在 using 中。

      【讨论】:

        【解决方案4】:

        如果您需要显式处理语句期间可能发生的不同异常情况,您可以将using 替换为try/catch/finally 并在finally 中显式调用Dispose()。或者您可以在using 块周围放置一个try/catch,以将特殊情况与确保处置分开。

        using 所做的唯一 事情是确保即使在块内抛出异常也调用 Dispose()。这是通用try/[catch]/finally 结构的一个非常有限的、高度具体的实现。

        重要的是,这些选项都没有任何实际影响 - 只要它满足您的需要,可读且易于理解,谁在乎?额外的尝试不会成为瓶颈之类的!

        回答您的最后一个问题 - 不,IDisposable 绝对不一定意味着实施者拥有非托管资源的句柄。这是一个比这更简单和更通用的模式。这是一个有用的例子:

        public class Timer : IDisposable
        {
            internal Stopwatch _stopwatch;
            public Timer()
            {
                this._stopwatch = new Stopwatch();
                this._stopwatch.Start();
            }
        
            public void Dispose()
            {
                this._stopwatch.Stop();
            }
        }
        

        我们可以使用它来计时,而不必显式依赖 start 和 stop 被调用:

        using(Timer timer = new Timer())
        {
            //do stuff
        }
        

        【讨论】:

          【解决方案5】:

          using 构造是try/finally 块的简写。即编译器生成:

          FileStream fs = null;
          try
          {
              fs = File.Open(path);
              // ...
          }
          finally
          {
              if (fs != null)
                  fs.Dispose();
          }
          

          因此,如果您不需要catch,则使用using 是正确的,但如果您需要,那么您应该使用普通的try/catch/finally

          【讨论】:

          • using 本身包装在try/catch 中没有任何问题。它比finally 更清晰,因为无需查看finally,在块退出后立即释放了哪些资源。