【问题标题】:Getting rid of nested using(...) statements摆脱嵌套的 using(...) 语句
【发布时间】:2010-05-16 19:02:07
【问题描述】:

有时我需要在一个函数中使用多个一次性对象。最常见的情况是拥有 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
}

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

        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 函数,以便该类更加灵活。 滥用这个函数肯定会导致对象没有被正确处理,但是如果没有这个类,还有很多其他的方法可以让你自己开枪。

【问题讨论】:

  • IMO - 在许多层面上都是个坏主意。我希望您在这里找到指导,为了您自己以及您可能与之合作的任何团队成员。请参阅 Skeet 的回答以了解清楚。
  • @Simon:别傻了。我们在这里一直有关于命名、大写和缩进的问题。相比之下,这个相当可观。
  • 不确定我的概念是否有误,但不是为了这个目的,我们有来自 WinForms 时代的System.ComponentModel.Container?使用简单的Container 就可以了,这就像一个带有 Add 方法的集合。

标签: c# idisposable using


【解决方案1】:

你可以这样做:

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

【讨论】:

  • 我只会添加此评论一次,即使三个人说了同样的话。如果您将 stylecop 作为编码标准的一部分,我认为它不会喜欢这样,因为它违反了禁止如果没有花括号的规则
  • 该死!这就是从 IntelliSense 自学语言对您的作用!我已经使用 C# 多年了,还没有想过以这种方式链接 using 语句:(
  • @Stewart - 不过,这是 stylecop 的一个缺陷。允许这样使用语句“堆叠”是有意的;请注意 IDE 如何不尝试缩进其他使用行。
  • 遵守 Stylecop(或 FxCop)的每条建议有点像跟随 GPS 装置进入湖中。有时您需要使用自己的判断力(哪个软件无法做到。)
  • 我从来没有说过 stylecop 是对的——这只是基于我自己的经验的观察——只是如果在你的代码库中打开它,禁用它的编译指示比原始代码更丑:)
【解决方案2】:

关于一般原则的几点说明:

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

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

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

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

基本上,我认为这是个坏主意。嵌套两层深度并不痛苦。嵌套三个应该很少见;嵌套四个或更多强烈建议重构。与其试图应对深度嵌套的痛苦,不如设计远离它。

【讨论】:

  • +1 表示“坏主意”。我的感觉在隐式块中走得更远,除非代码永远不会再被触及,否则就是等待发生的错误。我总是无一例外地使用显式块。正如你所说,如果我对嵌套感到困扰,我会远离它进行设计。
  • 我不完全确定,但我对 using 语句的理解意味着您的两个相同类型变量的示例是不安全的。我很确定这两个对象一开始就不能相互依赖,因为构造顺序可能是不确定的,但如果第二个构造函数抛出,我很确定第一个对象不会被处置。
  • +1 建议拆分成更小的方法。另外,+1 @Sky 建议总是无一例外地有块。
  • +1 为此:“您的代码显然是非惯用的 C#。基本上,您是在要求使用您的代码的其他人采用一种不寻常的风格,但收益甚微。”跨度>
  • @nawfal:是的,我通常会这样做。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

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

    此外,没有理由将IDisposables 的列表暴露给外界。
    因此,您不应该继承任何集合类,而是维护一个私有的List&lt;IDisposable&gt;

    然后您应该有公共的Add&lt;T&gt;Dispose 方法(并且没有其他方法),并在Dispose 中向后循环列表。

    【讨论】:

      【解决方案5】:

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

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

      using (var stream = File.Open(...))
      using (var reader = new StreamReader(stream)) {
      
         // do stuff
      
      }
      

      【讨论】:

        【解决方案6】:

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

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

        在我看来,这是非常不可读的 - 任何未包装的代码块都是不好的风格,除非所有开发人员都非常小​​心,否则可能会导致问题。

        我会把它与类似的东西相提并论:

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

        从技术上讲,它不是无效的,但看起来很糟糕。

        我同意 MultiDispose 概念可能不是最好的想法,因为它不是一种公认​​的模式,但我也绝对不会走这条路。如果你不能把事情分解成更小的块,那么我建议只使用嵌套的 usings。

        【讨论】:

          猜你喜欢
          • 2011-03-21
          • 2018-11-18
          • 1970-01-01
          • 2010-11-22
          • 2013-12-15
          • 2014-06-05
          • 2014-04-04
          • 2020-08-04
          相关资源
          最近更新 更多