如何从一个方法中返回一个流,并知道它应该被释放?

45
我有一个方法,它以 FileStream 作为输入。这个方法在一个 for 循环中运行。
private void UploadFile(FileStream fileStream)
{
    var stream = GetFileStream();
    // do things with stream
}

我还有另一种方法,它创建并返回了FileStream
private FileStream GetFileStream()
{
    using(FileStream fileStream = File.Open(myFile, FileMode.Open))
    {
        //Do something
        return fileStream;
    }
}

现在第一种方法在我尝试访问返回的FileStream时抛出了ObjectDisposedException异常,可能是因为它已经关闭了,因为我使用using来正确处理流。
如果我不使用using,而是按照以下方式使用它,那么FileStream将保持打开状态,循环的下一次迭代(操作相同的文件)将抛出一个告诉文件已经在使用中的异常:
private FileStream GetFileStream()
{
    FileStream fileStream = File.Open(myFile, FileMode.Open);
    //Do something
    return fileStream;
}

如果我使用try-finally块,在finally中关闭流,那么它也会抛出ObjectDisposedException异常。
我该如何有效地返回FileStream并关闭它?

5
不是你的工作,你不能关闭它。使用好的名称。“Get”不足以帮助程序员理解他需要处理流,使用“Create”代替。 - Hans Passant
4个回答

51

当你从方法中返回一个 IDisposable 对象时,你将释放它的责任交给了调用方。因此,在你的代码中,需要在整个流使用范围内声明 using 块,这在你的情况下可能跨越了 UploadFile 调用。

using (var s = GetFileStream())
    UploadFile(s);

23
问题在于FileStream对象在退出GetFileStream()方法时就被释放了,导致它处于不可用状态。正如其他答案所指出的那样,您需要从该方法中移除using块,并将using块放置在调用此方法的任何代码周围:
private FileStream GetFileStream()
{
    FileStream fileStream = File.Open(myFile, FileMode.Open);
    //Do something
    return fileStream;
}

using (var stream = GetFileStream())
{
    UploadFile(stream);
}

然而,我希望更进一步。你想要一种保护由GetFileStream()创建的流免受懒惰程序员可能在没有using块的情况下调用该方法的影响的方法,或者至少以某种方式强烈提示调用方这个方法的结果需要用using块括起来。因此,我建议这样做:

public class FileIO : IDisposable
{
    private FileStream streamResult = null;

    public FileStream CreateFileStream(string myFile)
    {
        streamResult = File.Open(myFile, FileMode.Open);
        //Do something
        return streamResult;
    }

    public void Dispose()
    { 
       if (streamResult != null) streamResult.Dispose();         
    }

}

using (var io = FileIO())
{
    var stream = io.CreateFileStream(myFile);

    // loop goes here.
}

请注意,您不一定需要创建一个全新的类来实现这个功能。您可能已经有了一个适当的类,在这个方法中可以添加IDisposable代码。最重要的是,您可以使用IDisposable作为一个信号,告诉其他程序员这段代码应该用using块包装。

此外,这使您可以修改类,以便在循环之前创建您的IDisposable对象,并使新的类实例跟踪您需要在循环结束时处理的所有内容。


1
这是一个很好的回答!@Joel,您介意详细说明一下如何使用IDisposable作为向其他程序员发出信号的方法吗?其他程序员如何知道在实例化此类时,他们应该将自己的代码包装到using块中? - jrn
4
这如何有所帮助? Stream 类已经实现了 IDisposable 接口。 此外,在 using(var io = FileIO()) 块内多次调用 io.CreateFileStream(..) 不会出现问题。 - oɔɯǝɹ

5
如果您有一个需要返回打开文件流的方法,那么该方法的所有调用者都需要负责处理返回的流,因为它不能在返回流之前处理流。

-2

我不确定如何阅读问题中的代码,因为UploadFile方法接收fileStream,但随后通过GetFileStream创建自己的stream,并且根本没有使用fileStream

但是我有一个建议,可能也可以解决类似的问题。 它被称为“工厂隔离模式”(来自Gary McLean Hall的书“通过C#进行自适应编码”) 这个想法是将对象的创建和销毁放在一起,但仍然允许以灵活的方式使用对象。而且只需要稍微改变@frankmartin原始的GetFileStream方法,我们将事情反过来,而不是让可处理对象逃逸,我们让“做某事”进入其中:

private void With(Action<FileStream> do)
{
    using (FileStream fileStream = File.Open(myFile, FileMode.Open))
    {
        do(fileStream);
    }
}

然后您可以这样使用此方法:

With(fileStream => UploadFile(fileStream);

在这里,用户不能忘记处理流(正如@oɔɯǝɹ所指出的那样),事实上,用户甚至不需要知道它必须以任何特殊方式被处理或照顾...


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