15

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 非托管资源所有者的类型?与我朋友的讨论指出答案是否定的。(我也在这个论坛的使用部分阅读了一些主题,那里有一些非常好的知识)。

4

5 回答 5

7

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

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

于 2009-09-02T20:44:57.273 回答
7

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

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

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

{
    FileStream fs;
    try
    {
        fs = File.Open(path);

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

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

于 2009-09-02T20:59:08.430 回答
4

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

唯一要做的就是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
}
于 2009-09-02T20:40:15.363 回答
4

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

于 2009-09-02T20:42:01.070 回答
3

using构造是块的简写try/finally。也就是说,编译器生成:

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

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

于 2009-09-02T20:47:36.863 回答