C#中的嵌套using语句

363

我正在做一个项目。我需要比较两个文件的内容,看它们是否完全匹配。

在进行大量错误检查和验证之前,我的第一版是:

  DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory + "\\TestArea\\");
  FileInfo[] files = di.GetFiles(filename + ".*");

  FileInfo outputFile = files.Where(f => f.Extension == ".out").Single<FileInfo>();
  FileInfo expectedFile = files.Where(f => f.Extension == ".exp").Single <FileInfo>();

  using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
  {
    using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
      {
        if (outFile.ReadLine() != expFile.ReadLine())
        {
          return false;
        }
      }
      return (outFile.EndOfStream && expFile.EndOfStream);
    }
  }

在嵌套的using语句中似乎有点奇怪。

是否有更好的方法来实现这个?


我认为我可能已经找到了一种语法更清晰的方式来声明这个using语句,而且它似乎对我有效?在using语句中使用var作为类型,而不是IDisposable,似乎允许我实例化我的两个对象并调用它们所分配的类的属性和方法,就像这样:using(var uow = UnitOfWorkType1(), uow2 = UnitOfWorkType2()){} - hcp
可能是在C#中处理嵌套的"using"语句的重复问题。 - 200_success
@200_success 这个问题是在2009年被问到的,那个问题则是在2013年,所以如果有什么区别的话,我可能会更倾向于将重复标识翻转。 (2¢, fyi, 等等) - ruffin
17个回答

629

最好的方法是只在最后一个using语句之后放置一个左大括号{,像这样:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead())) 
{
    ///...
}

12
更清晰?同时也不会强制您使用相同的类型...即使类型匹配,为了可读性和一致性,我总是这样做。 - meandmycode
7
Visual Studio的自动格式化会将其删除。目的是让代码看起来像变量声明列表。 - SLaks
49
不确定我是否认为那样更易读。如果说有什么区别的话,它会破坏嵌套代码的外观。而且第一个using语句看起来是空的,没有被使用。但是,我想无论怎样都可以…… :/ - Jonathon Watney
10
@Bryan Watts,“反对者”可能表达了真实的偏好。如果嵌套被推荐,不同的开发人员组可能会提出异议,这是非常可能的。唯一确定的方法是在另一个平行宇宙中再次运行实验。 - Dan Rosenstark
6
“@fmuecke: 这并不完全正确;它会起作用。 IDisposable 的规则表明,调用 Dispose() 两次应该不会有任何影响。这个规则只是针对编写不良的可处置对象。” - SLaks
显示剩余12条评论

152

如果这些对象是相同类型,你可以进行以下操作:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
                    expFile = new StreamReader(expectedFile.OpenRead()))
{
    // ...
}

1
如果它们都是IDisposable类型,那么它们都是相同的类型,也许可以进行强制转换? - jpierson
9
@jpierson 的方法是可行的,但是当你在 using 块内部调用 IDisposable 对象时,我们不能调用任何类成员(除非进行强制转换,这种方式在我看来有违初衷)。 - Connell
IDisposable是一种类型,因此只需将其用作类型即可拥有混合类型列表,就像其他答案中所看到的那样。 - Chris Rollins

39

IDisposable 类型相同时,可以执行以下操作:

 using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
     expFile = new StreamReader(expectedFile.OpenRead()) {
     // ...
 }

在MSDN关于using的页面上有这个语言特性的文档。

无论IDisposable是否是相同类型,您都可以执行以下操作:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamWriter anotherFile = new StreamReader(anotherFile.OpenRead()))
{ 
     // ...
}

27

自从C# 8.0版本以后,你可以使用using声明

using var outFile = new StreamReader(outputFile.OpenRead());
using var expFile = new StreamReader(expectedFile.OpenRead());
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
    if (outFile.ReadLine() != expFile.ReadLine())
    {
         return false;
    }
}
return (outFile.EndOfStream && expFile.EndOfStream);

这将在变量作用域结束时处理使用的变量,即在方法结束时。


使用using var outFile = new StreamReader(outputFile.OpenRead()), var expFile = new StreamReader(expectedFile.OpenRead());也可以工作。 - Chazt3n
@Chazt3n 不可以。隐式类型变量不能有多个声明符。 - Paul Childs

19

如果你不介意在使用块之前声明变量,你可以在同一个using语句中声明它们所有的变量。

    Test t; 
    Blah u;
    using (IDisposable x = (t = new Test()), y = (u = new Blah())) {
        // whatever...
    }

这样,x和y只是IDisposable类型的占位符变量,供using块使用,而您在代码中使用t和u。只是想提一下。


6
我觉得对于一个新手开发者来说,看你的代码可能会感到困惑。 - Zack
8
这可能是一个不好的实践方式。它会导致变量仍然存在,即使未受控制的资源已被释放。根据微软C#参考文档,“您可以实例化资源对象,然后将变量传递给using语句,但这不是最佳实践。在这种情况下,即使控件离开using块,该对象仍然在作用域内,尽管它可能不再访问其未管理的资源。” - Robert Altman
@RobertAltman 你说得对,在实际的代码中我会使用另一种方法(可能是Gavin H的方法)。这只是一个不太优先考虑的替代方案。 - Botz3000
你可以将声明与类型转换放在 using 语句内部,这样会更好吗? - Timothy Blaisdell

11

使用using语句是基于IDisposable接口的,因此另一种选择是创建某种类型的复合类,该类实现IDisposable并具有对您通常放入using语句中的所有IDisposable对象的引用。这样做的缺点是,您必须首先在作用域之外声明变量,然后才能在using块中使用它们,这需要比其他建议更多的代码行。

Connection c = new ...; 
Transaction t = new ...;

using (new DisposableCollection(c, t))
{
   ...
}

在这种情况下,DisposableCollection的构造函数是一个参数数组,因此您可以输入任意数量的参数。

8
如果您想高效比较文件,则完全不需要使用StreamReaders,因此不需要使用usings-您可以使用低级别的流读取来拉入数据缓冲区以进行比较。您还可以首先比较文件大小,以快速检测不同的文件,以节省读取所有数据的时间。

是的,检查文件大小是个好主意,可以节省读取所有字节所用的时间。 (+1) - TimothyP
1
这似乎没有解决问题,问题是关于嵌套using语句的(无论using语句包含什么)。这应该是一个注释。 - TylerH

7

您可以使用逗号在一个using语句中将多个可释放对象分组:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
       expFile = new StreamReader(expectedFile.OpenRead()))
{

}

7

你也可以这样说:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
   ...
}

但有些人可能会觉得这很难阅读。顺便提一下,作为解决问题的优化,为什么不先检查文件大小是否相同,然后再逐行进行呢?


6
您可以使用以下方式省略除最内层外的所有括号:
using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
  while (!(outFile.EndOfStream || expFile.EndOfStream))
  {
    if (outFile.ReadLine() != expFile.ReadLine())
    {
      return false;
    }
  }
}

我认为这比其他人建议的将同一类型的内容放在同一个 using 中更加清晰,但我相信很多人会觉得这很令人困惑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接