8

我想听听您对以下主题的看法:

想象一下,我们有一个负责实现一个特定目的的方法,但要做到这一点,它需要大量本地范围对象的支持,其中许多都实现了IDisposable.

MS 编码标准说,当使用IDisposable不需要“存活”方法范围的本地对象时(不会返回或不会分配给某些较长寿命的状态信息object),您应该使用该using构造。

问题是,在某些情况下,您可以获得嵌套的“地狱”using块:

using (var disposableA = new DisposableObjectA())
{
     using (var disposableB = new DisposableObjectB())
     {
          using (var disposableC = new DisposableObjectC())
          {
               //And so on, you get the idea.
          }
     }
}

如果您使用的某些对象派生自一个公共基础或实现一个interface实现IDisposable. 这当然是以在需要对象的真实类型时必须强制转换所述对象为代价的。有时这可能是可行的,只要施法量不会失控:

using (var disposableA = new DisposableObjectA())
{
     using (DisposableBaseObject disposableB = new DisposableObjectB(),
            disposableC = new DisposableObjectC)
     {
          using (var disposableD = new DisposableObjectD())
          {
               //And so on, you get the idea.
          }
     }
}

另一种选择是不使用using块并直接实现try-catch块。这看起来像:

DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...

try
{
    disposableA = new DisposableObjectA();
    ....
}
finally
{
     if (disposableA != null)
     {
          disposableA.Dispose();
     }

     if (disposableB != null)
     {
          disposableB.Dispose();
     }

     //and so on
}

有趣的是,VS Code Analyzer 会将此代码标记为“错误”。它将通知您,并非所有可能的执行路径都能确保所有一次性对象都在超出范围之前被释放。我只能看到,如果在处理时抛出了某个对象,我认为这永远不会发生,如果确实发生了,这通常表明事情正在发生,你可能最好像你一样快速优雅地退出可以从您的整个应用程序中。

所以,问题是:你更喜欢哪种方法?using不管有多少嵌套块总是更可取,或者超过一定限制,最好使用try-catch块?

4

3 回答 3

16

如果只有一个语句,则不需要大括号,例如:

using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
{
               //And so on, you get the idea.
}

不过,这确实取决于外部块中没有发生任何其他事情。

于 2011-06-03T12:50:15.890 回答
7

我认为您忘记了该using语句(与许多其他语句一样)不一定需要代码块,但可以是单个语句。你的第一个例子可以写成:

using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
{
    //And so on, you get the idea.
}

我认为这极大地缓解了问题。请注意,如果您需要在调用实现IDisposable.

我什至会嵌套其他有意义的块。 foreach是一个例子。

IEnumerable<int> ints = ...;

using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
foreach (int i in ints)
{
    // Work with disposableA, disposableB, disposableC, and i.
}

应该注意的是,当 VS Code 分析器告诉您这是不正确的时,它是正确的:

DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...

try
{
    disposableA = new DisposableObjectA();
    ....
}
finally
{
     if (disposableA != null)
     {
          disposableA.Dispose();
     }

     if (disposableB != null)
     {
          disposableB.Dispose();
     }

     //and so on
}

当您使用using堆叠在彼此之上时,它将它们嵌套在多个try/finally块中,如下所示:

DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...

try
{
    disposableA = new DisposableObjectA();

    try
    {
        disposableB = new DisposableObjectB();

        // Try/catch block with disposableC goes here.
    }
    finally
    {
         if (disposableB != null)
         {
              disposableB.Dispose();
         }    
    }
}
finally
{
     if (disposableA != null)
     {
          disposableA.Dispose();
     }    
}

disposableA.Dispose在您的示例中,如果在执行时抛出异常,disposableBdisposableC不会被处理(退出块),如果在调用finally时抛出错误,则不会关闭,等等。disposableBdisposableC

于 2011-06-03T12:51:08.367 回答
1

您的第一个代码示例唯一真正的“问题”是深度嵌套,这会使阅读和维护代码变得困难。作为建议您简单地删除花括号的其他答案的替代方案,您还可以通过将嵌套最深的代码重构为单独的函数来解决此问题。这将创建和处理一次性对象与实际使用它们的问题分开。

using (var a = new DisposableObjectA())
{
    using (var b = new DisposableObjectB())
    {
         using (var c = new DisposableObjectC())
         {
              SomeFunction(a,b,c);
         }
    }
}
于 2011-06-03T12:55:05.540 回答