C# 使用(…)语句摆脱嵌套

C# 使用(…)语句摆脱嵌套,c#,idisposable,using,C#,Idisposable,Using,有时我需要在一个函数中使用几个一次性对象。最常见的情况是有StreamReader和StreamWriter,但有时甚至不止这些 嵌套的using语句很快就会累加起来,看起来很难看。 为了解决这个问题,我创建了一个小类,该类收集IDisposable对象,并在其自身被释放时对其进行处理 public class MultiDispose : HashSet<IDisposable>, IDisposable { public MultiDispose(params IDisp

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

嵌套的using语句很快就会累加起来,看起来很难看。 为了解决这个问题,我创建了一个小类,该类收集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
}
这种方法是否有任何错误,可能会在将来引起问题? 我特意保留了从HashSet继承的Remove函数,以便该类更加灵活。
当然,误用此功能可能会导致无法正确处理对象,但如果没有此类功能,还有许多其他方法可以击中自己的脚。

您可以这样做:

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

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

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

只需使用一对大括号,就可以使嵌套的
using
语句更漂亮,如下所示:

        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
        }
using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{
    ///...
}

要回答您的问题,您需要按与添加相反的顺序进行处理。
因此,您不能使用
哈希集

此外,没有理由向外界公开
IDisposable
s列表。
因此,您不应该继承任何集合类,而是应该维护一个私有的
列表


然后,您应该使用public
Add
Dispose
方法(而不是其他方法),并在
Dispose
中向后循环列表,这会让我发疯。如果您发现嵌套的using语句很烦人,那么可以恢复到try/finally语法。Dispose方法不应该抛出异常,因此您可以假设多个Disposable不需要单独包装在try/finally块中

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

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

   // do stuff

}

关于一般原则的几点:

  • 您的代码显然不是惯用的C语言。基本上,您要求使用您的代码的任何其他人采用一种不寻常的风格,但几乎没有什么好处
  • 正如其他人所指出的,可以使用
语句嵌套
,而不需要额外的大括号
如果你发现自己在一个方法中有很多使用语句,你可能会想把它分解成更小的方法
  • 如果有两个相同类型的变量,则可以使用单个using语句:

    using (Stream input = File.OpenRead("input.dat"),
           output = File.OpenWrite("output.dat"))
    {
    }
    
  • 现在假设你真的想继续做这个:

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

    基本上,我认为这是个坏主意。在两层深的地方筑巢远非痛苦;筑巢三个应该是罕见的;嵌套四个或四个以上强烈建议重构。与其试图解决深嵌套带来的痛苦,不如远离它进行设计。

    我不得不说,我不同意那些想一个接一个地使用语句的人,比如:

    using (var a = new StreamReader())
    using (var b = new StreamWriter())
    {
     // Implementation
    }
    
    在我看来,这是非常不可读的——任何未包装的代码块都是糟糕的风格,除非所有开发人员都非常小心,否则可能会导致问题

    我把它放在和

    类似的东西上。
    if (someBoolean) DoSomething();
    {
      // Some code block unrelated to the if statement
    }
    
    从技术上讲,它不是无效的,但它看起来很可怕


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

    我只添加一次这个评论,即使有三个人说过同样的话。如果你把stylecop作为你的编码标准的一部分,我认为它不会像这样,因为它违反了禁止没有花括号的If的规则!这就是从IntelliSense自学一门语言对你的影响!我已经使用C#多年了,没有想过用这种方式链接语句:(@Stewart-这是stylecop中的一个缺陷。允许使用语句像这样“堆叠”是有意的;注意IDE如何不尝试使用其他行缩进。遵守stylecop(或FxCop)每一个推荐都有点像跟随一个GPS装置进入湖中。有时你需要运用你的判断(这是软件无法做到的)我从来没有说过stylecop是对的-这只是基于我自己的经验的观察-只是如果在代码库中打开它,禁用它的杂注比原始代码更难看:)+1表示“坏主意”。我的感觉在隐式块中更进一步,除非代码再也不会被触动,否则bug就要发生了。我总是毫无例外地使用显式块。正如你所说,如果我对嵌套感到困扰,我会远离它进行设计。我不完全确定,但我对使用语句的理解意味着你的两个相同类型变量的例子是不安全的。我很确定这两个对象在一开始就不能相互依赖,因为构造顺序可能是不确定的,但如果第二个构造函数抛出,我也很确定第一个对象将不会被处理。+1表示建议分解成更小的方法。另外,+1@Sky表示总是毫无例外地使用块,+1表示:“您的代码显然是非惯用的C#。基本上,您是在询问任何在