使用语句和try-catch()-finally重复代码块?

16

using语句是一个try{}finally{}的简写形式。

但如果我有以下这样的using语句:

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


}
现在我想捕获打开此文件可能引起的异常(由于环境问题,这是相当高风险的代码),但如果我在内部编写try-catch,那么是否会重复?当代码被编译为IL并JIT时,假设重复将被删除?
然而,我想捕获打开文件可能引起的异常(因此应该在using语句的范围之外包装try-catch),以及在using块内所做的任何操作引发的异常,因此我应该在块内添加try-catch。
这似乎会给CLR内部已经处理的事情增加很多重复。CLR是否添加catch子句?
我的同事认为using语句很混乱(但这是因为单行有点长,因为我需要快速更改它们,并且无法访问代码库的其他部分)。该同事不使用using语句,但是在using语句和try-finally / try-catch-finally之间是否存在任何功能差异?我确实看到过一种情况,即WCF服务具有关于使用finally和返回值(大概是finally)的不太知名的边角案例。解决方案是使用检查块。C#中是否有类似的情况?
另外,所有实现IDisposale接口的类型都拥有非托管资源的所有者吗?和我的朋友讨论后发现答案是否定的。(我还阅读了该论坛using部分的一些帖子,那里有很好的知识。)
5个回答

8
如果需要处理一些异常,您当然可以自己实现这个模式。但就我个人而言,我觉得将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 语句支持 finally 吗?你从哪里了解到使用匿名作用域块的?我想更多地了解这个。因此,当我在 using 块中打开一个文件(例如 FileSream.Open()),这个异常将会冒泡。如果 using 语句实现了 try/finally ,我必须将其包装在 try/catch 中才能使用 catch。 - GurdeepS

7

我建议保留using语句并将其包装在try/catch中。外部的try/catch将捕获您需要监视的任何异常,同时忽略Dispose()。这有一个额外的优点,如果您以后重新构建代码,将try/catch移动到其他地方(比如调用函数),它可以保护您免受自己的影响。

至于IDisposable的问题:任何人都可以出于任何原因实现它。从技术上讲,它没有被限制为非托管资源。(无论在您的代码中是否应该将其限制为非托管资源是另一个问题)。


4
使用和try-finally之间最大的区别是,使用只会调用Dispose()方法。如果你自己实现finally块,可以做其他逻辑,比如记录日志,这些将不包括在使用中。

4
如果您需要明确处理语句期间可能发生的不同异常情况,可以将using替换为try/catch/finally,并在finally中显式调用Dispose()。或者您可以在using块周围放置try/catch以将异常情况与确保处理分开。 using唯一的作用是确保即使在块内部抛出异常,也会调用Dispose()。这是一种非常有限、高度特定的通用try/[catch]/finally结构的实现。
重要的是,这些选项都没有任何真正的影响-只要它能满足您的需求,易于阅读和理解,谁关心呢?这不像额外的try会成为瓶颈之类的东西!
回答您最后一个问题-不,IDisposable绝对不一定意味着实现者具有对非托管资源的句柄。它比那更简单、更通用的模式。这是一个有用的例子:
public class Timer : IDisposable
{
    internal Stopwatch _stopwatch;
    public Timer()
    {
        this._stopwatch = new Stopwatch();
        this._stopwatch.Start();
    }

    public void Dispose()
    {
        this._stopwatch.Stop();
    }
}

我们可以使用这个方法来计时,而不需要显式地依赖于开始和结束的调用,方法如下:
using(Timer timer = new Timer())
{
    //do stuff
}

3
using结构是try/finally块的简写形式。也就是说,编译器会生成以下代码:
FileStream fs = null;
try
{
    fs = File.Open(path);
    // ...
}
finally
{
    if (fs != null)
        fs.Dispose();
}

因此,如果您不需要一个catch,使用using是适当的,但如果需要,则应该使用普通的try/catch/finally

2
using语句本身包装在try/catch中没有任何问题。这比使用finally更清晰,因为立即明显的是在块退出后释放了哪些资源,而不必查看finally - Pavel Minaev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接