关闭文件而不使用using

3
我有一个类,从一个文件流中读取数据并写入另一个文件流。我担心在closeFiles()函数完成处理后关闭流。
如果一个流的dispose抛出异常,阻止了对另一个流的dispose调用,你该如何处理?
我应该在流上调用close和dispose,还是只调用其中之一?
如果我捕获了流dispose时出现的任何错误,然后继续执行lastOperation()中所示的移动和删除文件操作会发生什么?
在完美的世界里,我想在C++风格的初始化列表中使用using语句,但我相信在C#中不可能实现这一点。
编辑:谢谢大家的快速回复。所以我应该从IDisposable派生,然后改变构造函数并添加这两个disposing方法,就像这样:
    ~FileProcessor()
    {
        Dispose(true);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                sw.Flush();
            }
            closeFiles();
            disposed = true;
        }
    }

我基本上正在做的是:

class FileProcessor
{
    private string in_filename;
    private string out_filename;
    private StreamReader sr;
    private StreamWriter sw;
    bool filesOpen = false;

    public FileProcessor(string filename)
    {
        in_filename = filename; 
        out_filename = filename + ".out";
        openFiles();
    }

    ~FileProcessor()
    {
        closeFiles();
    }

    private void openFiles()
    {
        sr = new StreamReader(in_filename);
        sw = new StreamWriter(out_filename);
        filesOpen = true;
    }

    private void closeFiles()
    {
        if (filesOpen)
        {
            sr.Close();
            sw.Close();
            sr.Dispose();
            sw.Dispose();
            filesOpen = false;
        }
    }

    /* various functions to read, process and write to the files */

    public void lastOperation()
    {
        closeFiles();
        File.Delete( in_filename );
        Directory.Move(out_filename, outdir + out_filename);
    }
}

关于编辑:您仍应该删除析构函数。并且分别检查sr、sw。 - H H
谢谢Henk,我在编辑后看到了你的个人检查添加,它已经在我的代码中:)。我得到了“建议”从http://msdn.microsoft.com/en-us/library/system.idisposable.dispose(VS.71).aspx保留析构函数,其中建议“使用C#析构函数语法进行最终化代码。只有在未调用Dispose方法时,此析构函数才会运行。”我理解这意味着如果对象被处理,析构函数不会很昂贵,但是在某些情况下将需要析构函数...我可能会开始另一个关于这个问题的问题。 - Patrick
Patrick,那个MSDN示例中的类具有非托管资源。您只有托管资源。 - H H
4个回答

3

如果你在类中使用了 IDisposable 对象,那么实现 IDisposable 接口是一个好习惯。

同时,在你的 Dispose() 实现中,请确保不要抛出异常。如果每个你释放的对象都能够保证这一点,那么你的客户端将会更加安全。


3

你的FileProcessor类不应该有析构函数。它没有用处,但却很昂贵。

它应该有一个Dispose()方法(并实现IDisposable接口),以调用closeFiles()。

就像@marcelo所回答的那样,Stream.Dispose()不应抛出异常。你可以依靠BCL类来实现这一点。

但是你应该检查每个Reader/Writer是否为null,以防第一个被打开而第二个失败:

if (sr != null) sr.Dispose();
if (sw != null) sw.Dispose();

你的`filesOpen`无法同时覆盖两个。

2

Dispose方法不应该抛出异常。甚至有一个代码分析工具警告了这一点。


Dispose方法只有在无法满足与被处理对象相关的预期后置条件时才应抛出异常。失败的Dispose方法不好,但是默默失败的Dispose方法更糟糕。 - supercat
@supercat:根据我的经验,那些在退出时发生故障的事情只会通过抛出异常让情况变得更糟。我发现最好的做法是记录错误,报告错误等,然后继续进行解缠。也许我在这里与行业规范不一致。我更倾向于C++,在C++中从析构函数中抛出异常基本上是禁忌。对于C#来说,建议是否有所不同?是否有相关链接我可以查阅? - Marcelo Cantos
限定词:我知道Dispose不是一个析构函数,但在我看来,它的操作原理基本相同,因此在Dispose中抛出异常的风险与析构函数的风险基本相同。也许我对此有所偏差,但了解行业规范仍然是很好的。 - Marcelo Cantos
@Marcelo Cantos:基本上,应该尽可能地设计处理器,使其不会失败,除非出现非常严重的问题。如果使用处理器的代码编写正确,则一个处理器的故障不应阻止其他处理器的展开,但由于处理器无法知道其调用者的编写方式,因此从处理器中抛出异常存在一定的风险。问题在于,如果仍然存在的对象状态已损坏并且没有抛出异常,则可能没有很好的方法来信号问题并显示发生了什么。 - supercat
@Marcelo Cantos:一种方法可能是让长寿命对象具有“损坏”标志,以及异常对象和堆栈跟踪,指示它如何变得损坏;如果在长期使用的对象变得损坏后尝试使用该对象,则该尝试可能会抛出一个异常,其中包含损坏异常作为InnerException。我没有看到过这样做,但这可能是一个不错的模式。不幸的是,我不知道如何使InnerException的堆栈跟踪与原始异常发生时的堆栈跟踪匹配。 - supercat
显示剩余2条评论

0
在C#中,确实存在using语句。提供给using语句的对象必须实现IDisposable接口。该接口提供Dispose方法,应释放对象的资源。
如果您的StreamReader和StreamWriter实现了IDisposable,您可以将它们放入using块中,在使用完毕后它们会被清理地处理。
using(var sr = new StreamReader(in_filename)) {
    // Perform reader actions
}
// Reader will now be disposed.

但是我在构造函数中打开了流,需要在类的生命周期内保持它处于打开状态... - Patrick
然后将类放入using块中。 - Greg
2
把类放在using块里?那是错误的建议,因为它做不到。你怎么能期望那种代码能正常运行呢? - Øyvind Bråthen
@Øyvind:这取决于FilesProcessor将如何使用。这可能是可行的,也可能不可行。 - H H

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