有比嵌套“using”更好的确定性处理模式吗?(涉及IT技术)

20

在C#中,如果我想确定性地清理非托管资源,可以使用"using"关键字。但对于多个相关对象,这会导致进一步嵌套:


在C#中,如果我想确定性地清理非托管资源,可以使用“using”关键字。但是,对于多个相互依赖的对象,这将导致越来越深的嵌套。
using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
{
    using (BufferedStream bs = new BufferedStream(fs))
    {
        using (StreamReader sr = new StreamReader(bs))
        {
            // use sr, and have everything cleaned up when done.
        }
    }
}

在 C++ 中,我习惯使用析构函数来像这样做:

{    
    FileStream fs("c:\file.txt", FileMode.Open);
    BufferedStream bs(fs);
    StreamReader sr(bs);
    // use sr, and have everything cleaned up when done.
}

在C#中有更好的方法来处理这个问题吗?还是我只能继续多层嵌套?

10个回答

41

您不必嵌套多个using语句:

using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
    // all three get disposed when you're done
}
在.NET Core中,有一种新的using语句,它允许你不使用括号,并且在当前作用域结束时进行处理。
结果为:

在.NET Core中,有一种新的using语句,它允许你不使用括号,并且在当前作用域结束时进行处理。

void MyMethod()
{
    using var fs = new FileStream("c:\file.txt", FileMode.Open);
    using var bs = new BufferedStream(fs);
    using var sr = new StreamReader(bs);
    // all three are disposed at the end of the method
}

13
这个代码也是嵌套的——每次嵌套使用都被视为单行代码块,就像if语句没有加{ }一样。你所做的只是避免按TAB键。 - Greg Hurlman
1
我同意Greg Hurlman的观点,这是一种格式技巧:就像if、while等,“using”控制单个语句,但通常与一组语句一起使用。 - Mike Dimmick
4
是的,这是一种格式化的技巧,但关键在于它是一个有用的技巧,并且对于刚接触C#的人来说并不明显。 - Eclipse
@GregHurlman,这并不是“仅仅嵌套而已”;您会发现,如果在Visual Studio 2012中进行自动格式化,多个单行if语句会逐个缩进,但上述多个单行using语句则不会。 - Ryan Lundy

8

3
您可以使用这种语法来简化代码:
using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
}

这是一种罕见的情况,我认为不使用 { } 来包围所有块是有意义的。

1

我之前实现过像Michael Meadows的解决方案,但他的StreamWrapper代码没有考虑到如果成员变量上调用Dispose()方法由于某种原因抛出异常,则随后的Dispose()将不会被调用,资源可能会悬空。更安全的方式是:

        var exceptions = new List<Exception>();

        try
        {
            this.sr.Dispose();
        }
        catch (Exception ex)
        {
            exceptions.Add(ex);
        }

        try
        {
            this.bs.Dispose();
        }
        catch (Exception ex)
        {
            exceptions.Add(ex);
        }

        try
        {
            this.fs.Dispose();
        }
        catch (Exception ex)
        {
            exceptions.Add(ex);
        }

        if (exceptions.Count > 0)
        {
            throw new AggregateException(exceptions);
        }
    }

InnerException 是只读属性。你打算如何编写它? - supercat

0

你可以省略花括号,就像这样:

using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
        // use sr, and have everything cleaned up when done.
}

或者使用常规的try finally方法:

FileStream fs = new FileStream("c:\file.txt", FileMode.Open);
BufferedStream bs = new BufferedStream(fs);
StreamReader sr = new StreamReader(bs);
try
{
        // use sr, and have everything cleaned up when done.
}finally{
   sr.Close(); // should be enough since you hand control to the reader
}

0

这会导致代码行数的净增加,但可读性有了实质性的提高:

using (StreamWrapper wrapper = new StreamWrapper("c:\file.txt", FileMode.Open))
{
    // do stuff using wrapper.Reader
}

StreamWrapper 在这里被定义:

private class StreamWrapper : IDisposable
{
    private readonly FileStream fs;
    private readonly BufferedStream bs;
    private readonly StreamReader sr;

    public StreamWrapper(string fileName, FileMode mode)
    {
        fs = new FileStream(fileName, mode);
        bs = new BufferedStream(fs);
        sr = new StreamReader(bs);
    }

    public StreamReader Reader
    {
        get { return sr; }
    }

    public void Dispose()
    {
        sr.Dispose();
        bs.Dispose();
        fs.Dispose();
    }
}

通过一些努力,StreamWrapper 可以被重构为更通用和可重用的。


0
需要注意的是,在创建一个基于另一个流的流时,通常会关闭传入的流。因此,为了进一步简化你的示例代码:
using (Stream Reader sr = new StreamReader( new BufferedStream( new FileStream("c:\file.txt", FileMode.Open))))
{
    // all three get disposed when you're done
}

2
注意:异常安全性失败。如果BufferedStream构造函数失败,这将不会关闭FileStream。与C ++ RAII相比,使用语法真的很棘手。 - Aaron

0

举个例子,假设您有以下内容:

一个名为1.xml的文件位于c:\下

一个名为textBox1的文本框,其多行属性已设置为ON。

const string fname = @"c:\1.xml";

StreamReader sr=new StreamReader(new BufferedStream(new FileStream(fname,FileMode.Open,FileAccess.Read,FileShare.Delete)));
textBox1.Text = sr.ReadToEnd();

不,你不能这样做 - 如果在构造过程中 BufferedStream 或 StreamReader 抛出异常,FileStream 将不会被处理,直到 GC 清理时才会被处理。你不想让文件句柄像那样漂浮着。 - Eclipse

0

不要嵌套使用语句,你可以手动编写.Dispose调用 - 但几乎肯定会在某个时候错过其中一个。

要么运行FxCop或其他能够确保所有实现IDisposable接口的类型实例都有.Dispose()调用的工具,要么处理嵌套。


-1

using语句是一种语法糖,它会被转换为:

   try
   {
      obj declaration
      ...
   }
   finally
   {
      obj.Dispose();
   }

你可以显式地在对象上调用Dispose方法,但这样做不够安全,因为如果其中一个对象抛出异常时,资源将无法被正确释放。

注意:不正确。对象初始化发生在try之外,即使有多个对象。使用(X a = f(),b = f()){g(a,b);}等同于X a = f(); X b = f(); try {g(a,b);} finally {b.Dispose(); a.Dispose();}对于异常安全有所区别。 - Aaron

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