摆脱嵌套的使用(...)语句

时间:2010-05-16 19:02:07

标签: c# idisposable using

有时候我需要在一个函数中使用几个一次性对象。最常见的情况是使用StreamReader和StreamWriter,但有时它甚至比这更多。

嵌套使用语句快速加起来并且看起来很难看。 为了解决这个问题,我创建了一个小类来收集IDisposable对象,并在它本身被处理时处理它们。

public class MultiDispose : HashSet<IDisposable>, IDisposable
{
    public MultiDispose(params IDisposable[] objectsToDispose)
    {
        foreach (IDisposable d in objectsToDispose)
        {
            this.Add(d);
        }
    }

    public T Add<T>(T obj) where T : IDisposable
    {
        base.Add(obj);
        return obj;
    }

    public void DisposeObject(IDisposable obj)
    {
        obj.Dispose();
        base.Remove(obj);
    }


    #region IDisposable Members

    public void Dispose()
    {
        foreach (IDisposable d in this)
        {
            d.Dispose();
        }

    }

    #endregion
}

所以我的代码现在看起来像这样:

        using (MultiDispose md = new MultiDispose())
        {
            StreamReader rdr = md.Add(new StreamReader(args[0]));
            StreamWriter wrt = md.Add(new StreamWriter(args[1]));
            WhateverElseNeedsDisposing w = md.Add(new WhateverElseNeedsDisposing());

            // code
        }

这种方法有什么问题可以导致问题吗? 我故意离开了从HashSet继承的Remove函数,以便该类更灵活。 当然滥用这个功能可能导致物体没有被正确处理掉,但是在没有这个类的情况下,还有很多其他方法可以射击自己。

6 个答案:

答案 0 :(得分:47)

你可以这样做:

using (var a = new A())
using (var b = new B())
{
    /// ...
}

答案 1 :(得分:20)

关于一般原则的几点:

  • 你的代码明显是非惯用的C#。基本上你要求任何使用你的代码的人采用一种不同寻常的风格,但收效甚微。
  • 正如其他人所指出的,你可以在没有额外支撑的情况下嵌套using语句
  • 如果您发现自己在单个方法中使用 lot 使用语句,则可能需要考虑将其分解为更小的方法
  • 如果您有两个相同类型的变量,则可以使用单个using语句:

    using (Stream input = File.OpenRead("input.dat"),
           output = File.OpenWrite("output.dat"))
    {
    }
    

现在假设你真的想继续这个:

  • 您的代码将以难以预测的顺序处理其包含的资源。它应该嵌入一个列表而不是使用集合,然后按照与Add的调用相反的顺序处理事务。
  • 没有理由从HashSet<T>或任何集合派生。您应该在类中将列表作为私有成员变量。
  • 如果其中一个Dispose调用失败,则不会进行任何其他Dispose调用;使用传统的using语句,每次调用Dispose都会在其finally块中进行。

基本上,我认为这是一个坏主意。深度嵌套两层远非痛苦;筑巢三应该是罕见的;嵌套四个或更多强烈建议重构。你应该远离它,而不是试图应对深层筑巢的痛苦。

答案 2 :(得分:13)

也许只是你展示了一个简单的例子,但我认为以下内容更具可读性。

 using (StreamReader rdr = new StreamReader(args[0])) 
 using (StreamWriter wrt = new StreamWriter(args[1])) 
 {     
   // code 
 }

答案 3 :(得分:7)

您可以通过仅使用一对大括号使嵌套的using语句更漂亮,如下所示:

using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{
    ///...
}

要回答你的问题,你需要按照相反的顺序处理 因此,您无法使用HashSet

此外,没有理由将IDisposable列表公开给外界 因此,您不应继承任何集合类,而应维护私有List<IDisposable>

然后,您应该使用公开Add<T>Dispose方法(而不是其他方法),并在Dispose中向后循环列表。

答案 4 :(得分:4)

就个人而言,这会让我疯狂。如果您发现嵌套的using语句很烦人,则可以恢复为try / finally语法。 Dispose方法不应该抛出异常,因此你可以假设多个一次性用品不需要单独包装在try / finally块中。

另外值得注意的是,您只需要一组括号用于相邻的使用块,例如:

using (var stream = File.Open(...))
using (var reader = new StreamReader(stream)) {

   // do stuff

}

答案 5 :(得分:2)

我必须说我不同意那些想要一个接一个地使用陈述的人:

using (var a = new StreamReader())
using (var b = new StreamWriter())
{
 // Implementation
}

在我看来,这是非常难以理解的 - 任何未被包装的代码块都只是糟糕的样式,并且可能会导致问题,除非所有开发人员都非常小​​心。

我把它与以下内容相提并论:

if (someBoolean) DoSomething();
{
  // Some code block unrelated to the if statement
}

从技术上讲,它并非无效,但看起来很糟糕。

我同意MultiDispose概念可能不是最好的主意,因为它不是一个可接受的模式,但我绝对不会走这条路。如果你不能将事情分解成更小的块,那么我建议只使用嵌套的使用。