嵌套的Using语句有用吗?

4

在处理诸如打开数据库连接之类的资源密集型操作时,使用Using块被认为是最佳实践。由于Using“即使发生未处理的异常也保证资源的释放”

以下是我找到的大多数示例的写法:

Sub ExecuteCommand(ByVal sql As String, ByVal connectionString As String)
    Using connection As New SqlConnection(connectionString)
        Dim command As New SqlCommand(sql, connection)
        command.Connection.Open()
        command.ExecuteNonQuery()
    End Using
End Sub

但是嵌套的 Using 块是允许的,我偶尔(但很少)看到上面的代码被写成:

Sub ExecuteCommand(ByVal sql As String, ByVal connectionString As String)
    Using connection As New SqlConnection(connectionString)
        Using command As New SqlCommand(sql, connection)
            command.Connection.Open()
            command.ExecuteNonQuery()
        End Using
    End Using
End Sub

我的问题是:多个嵌套的 Using 块有什么好处吗?还是单个 Using 块已经保证了其包含的所有资源会被处理?
(注意:我的代码是用VB.NET编写的,但同样的问题也适用于C#。)

嵌套的“Using”在与图形相关的代码中非常常见,您可能同时使用Graphics对象、Brush、一些Pens和Bitmap。 - Ňɏssa Pøngjǣrdenlarp
5个回答

10
使用using块“保证在发生未处理的异常情况下,资源可以被正确处理和释放”。但该保证需要谨慎对待。有许多因素可能防止资源的释放。例如,如果using块包含无限循环,或者该块抛出异常时,堆栈中更高级别的异常过滤器进入无限循环并永远不会返回控制到与using语句相关的finally块,或者该块调用Environment.FailFast等情况。有许多事情可能导致资源永远无法释放,因此永远不要编写依赖于资源释放来确保其正确性的程序。资源释放只是为了礼貌地将它们返回到池中供其他人使用。
此外,请明确一点:在C#中,真正未处理的异常是实现定义的行为。using块的finally子句旨在处理在using块内引发异常并在其他位置处理的情况,而不是处理引发异常且从未处理的情况。如果发生这种情况,则完全由实现来确定会发生什么;C#语言对于抛出未处理异常的程序的行为没有做出任何承诺。资源可能会被处理,也可能不会。你正在一个即将被意外拆除的建筑物中,你真的想花时间洗碗并把它们整理好吗?

多个嵌套使用块有什么好处?

有。

单个Using块已经保证其包含的所有资源都将被处理和释放吗?

不是。只有实际上由using语句提到的资源会被清理。这就是为什么需要嵌套它们的原因。

在某些情况下,从技术上讲,您不必这样做,因为内部using块会负责释放与外部相同的资源。但是嵌套使用块并不会损害任何东西,并且可以让读者清楚地了解正在发生什么。在这里,最佳实践是为每个需要清理的资源添加一个using语句。


7

嵌套的using块确实很有用:单个块只会在自己的变量上调用Dispose,而不是在同一块内可能打开的其他变量上调用。这就是为什么每个你想要在程序中的一个定义点清理的变量(应该是每个实现了IDisposable类型的变量)都需要其自己的using块。


回答“一个Using块是否已经保证了它所包含的所有资源都会被处理?”将获得+1分。 - Karl Anderson

3
Using语句将释放在Using行中声明的变量。
它不会影响块其他地方声明的变量。
每个可处置变量都应始终使用Using语句。

完全不反对您的答案,但最好明确指出问题的第二部分“单个Using块是否已经保证了其包含的所有资源都将被处理?”的答案是“否”。 - Karl Anderson

1
嵌套的using语句非常有用。但是很多程序员没有考虑到SqlCommand对象需要进行资源清理。也就是说,资源是连接而不是命令。
添加: SqlCommand没有Close()方法,因此即使SqlCommand有Dispose()方法,缺少Close()方法表明实际上没有 "需要关闭/释放"的内容。可以肯定的是,一旦可处理的实例将被最终处理。很难找到一些权威的东西,而不用挖掘源代码,但当在this article中问到微软的人正在询问它处理了什么时,他说:"实际上没有太多...",然后建议使用SqlCommand()的USING子句,所以我最初回答了这个问题,但避免了其中的模糊性。

当你说“很多程序员认为…”时,这是否代表了语言的实际操作方式?也就是说,当连接关闭时,命令会被丢弃吗?这里的其他答案似乎并不支持这种观点。 - Doug

1

所有其他答案都是正确的。我想再补充一点。

如果对象是相同类型的,C#编译器提供了一种方式(语法糖),可以在单个using语句中内联使用多个对象。

C#版本

using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream())
{

}

VB.Net 版本
Using ms1 = New MemoryStream(), ms2 = New MemoryStream()

End Using

这将处理两个MemoryStream。

有意思。VB.NET允许类似的东西吗? - Doug
由于某种原因,人们似乎不使用这种方式代替嵌套语法。我一直认为这种方式更优越。 - Gary Walker
@GaryWalker:在大多数情况下,您不会同时使用两个相同类型的资源。在C#中,您可以省略除最内层using块之外的所有花括号(和相应的缩进),尽管并非每个人都喜欢这种策略。我不确定这种技术是否适用于VB.Net。 - Brian
当然,只有在应用时才有用。但是,当你以这种方式编写它时,资源之间的关系更加明显。 - Gary Walker

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