使用块丰富?

时间:2011-06-03 12:46:45

标签: c# code-analysis code-design

我希望您就以下主题发表意见:

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

MS编码标准表示当使用本地IDisposable对象时,不需要“存活”方法的范围(不会返回或不会被分配给某些更长寿的状态信息{{1}例如)你应该使用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
有趣的是,VS Code Analyzer会将此代码标记为“错误”。它将通知您,并非所有可能的执行路径都确保在超出范围之前处置所有一次性对象。我只能看到发生的事情,如果某些物体在处置时会抛出我认为永远不会发生的事情,如果确实如此,那通常表明某些事情真的搞砸了,你可能会像你一样快速优雅地退出可以从你的整个应用程序。

所以,问题是:你更喜欢什么方法?是否总是更喜欢使用嵌套的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块?

3 个答案:

答案 0 :(得分:16)

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

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

这确实取决于外部块中没有其他事情发生。

答案 1 :(得分: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块已退出),如果在调用disposableB时抛出错误,则disposableC未关闭,等等。

答案 2 :(得分:1)

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

using (var a = new DisposableObjectA())
{
    using (var b = new DisposableObjectB())
    {
         using (var c = new DisposableObjectC())
         {
              SomeFunction(a,b,c);
         }
    }
}