在“using”语句中创建的实例的返回

6
如果我有这个方法:
public StreamReader GetResourceStream(string filename)
{
    using (Stream stream = this.GetType().Assembly.GetManifestResourceStream(filename))
    using (StreamReader sr = new StreamReader(stream))
    {
        return sr;
    }
}

当我调用它时,我应该这样调用。
StreamReader resource = GetResourceStream("blah.xml");

或者这样:
using (StreamReader resource = GetResourceStream("blah.xml"))
{
    // Do stuff
}

如果采用第二种方式,这是否意味着使用using语句的using (StreamReader sr = new StreamReader(stream))行没有任何区别?
3个回答

7

您应该更改方法本身而不是包含一个using语句,但是确保调用代码使用一个using语句:

public StreamReader GetResourceStream(string filename)
{
    return new StreamReader(GetType().Assembly
                                     .GetManifestResourceStream(filename));
}

using (StreamReader resource = GetResourceStream("blah.xml"))
{
    // Do stuff
}

那样做只有在您实际使用时才会处理资源。基本上,该方法将返回的资源的所有权交给了调用者。这并不太糟糕 - 这是相当常见的,比如 File.OpenText 等。
当您将资源传递到方法或构造函数中时,情况就会变得更加棘手 - 被调用的代码是否应该拥有所有权?通常不需要,但偶尔也是合适的 - 例如,Bitmap(Stream) 文档说明您必须在位图所需的时间内保持流处于活动状态。事实上,释放位图还会释放流,因此您仍然只需要跟踪一个资源... 但您确实需要知道不能关闭流。

根据您的代码,我明白您并不需要像我一样在中间创建一个 Stream,而只需直接创建一个 StreamReader,但我很好奇,如果您需要创建一个 Stream 并将其传递给 StreamReader 的构造函数,您该如何做呢?(在我所知道的某些情况下,您需要创建一个中介流)。 - Ahmet
@Ahmet:请注意,我的代码创建的流的数量和你的一样,只是它不使用单独的本地变量来创建流。 - Jon Skeet
当您处理 StreamReader 时,它会处理其中包含的 Stream - Jon Skeet
@JonSkeet:非常感谢您的即时回答。我的担忧是,它必须是规范的一部分,而实现是确保弱点。更一般地说,当两个(或多个)可丢弃资源使用被嵌套时,这总是一个问题,但不是通过两个嵌套的using结构在语法上,而是通过控制流程... - g.pickardou
@g.pickardou:这取决于嵌套的方式以及负责的类。例如,StreamReader(Stream) 构造函数的文档明确说明:“当调用 StreamReader.Dispose 时,StreamReader 对象将在提供的 Stream 对象上调用 Dispose()。”所以我认为可以放心依赖它。如果您正在处理某些没有提供太多文档的东西,则会更加棘手。 - Jon Skeet
显示剩余2条评论

3

GetResourceStream方法放置用于返回对象的using语句实际上没有意义。这意味着你将返回已处于disposed状态的对象,这有什么好处呢?它会向你抛出ObjectDisposed异常。

因此,请移除工厂方法内部的using语句,在实际使用时应用using语句。

代码应该像这样:

public StreamReader GetResourceStream(string filename)
{
    Stream stream = this.GetType().Assembly.GetManifestResourceStream(filename);
    return new StreamReader(stream)        
}

你是指GetResourceStream内部的using还是仅指第二个? - Ahmet
我可以从Jon的代码中看出,我不需要使用Stream,而是可以直接创建一个StreamReader,但现在我很好奇,如果我使用你上面的代码,谁会处理Stream stream对象的释放(不是StreamReader;现在我明白了,我会从调用者代码中处理它的释放? - Ahmet
2
@Ahmet 两个代码示例都创建了一个StreamStreamReader在被销毁时可能会调用底层流的dispose方法,尽管我还没有找到相关的文档来证实这一点。 - Paul Phillips
1
@Ahmet 在linqpad中进行的快速实验表明这是正确的。 StreamReader 会处理底层的 Stream - Paul Phillips
有一些情况下,在Using语句块内返回一个对象是有意义的。例如,绘制许多WinForms控件的代码将使用传入的Font属性,创建具有相同属性的新字体进行绘制,然后释放新字体。因此,控件不关心它们的Font属性是否设置为已释放的字体。在分配给控件之前立即释放字体可能看起来很奇怪,但这比试图弄清楚何时不再需要特定字体要容易得多。 - supercat
我不清楚是谁以及在哪里确保清除清单流(我指的不是StreamReader,而是在GetManifestResourceStream()调用内创建的Stream)。 - g.pickardou

2
当程序流程退出第二个using块时,sr.Dispose() 将被调用,释放 StreamReader 并使其无效。
在流程控制退出该块之前,您需要利用 sr。

所以如果我理解正确,我的GetResourceStream方法不应该使用第二个using,是吗? - Ahmet
@Ahmet:你需要在某个地方释放资源,但如果你要返回sr对象,你需要在使用它的地方释放资源,而不是在那里将其包装在using块中。 - Eric J.
@Ahmet:在回复中添加了我所说的示例。 - Eric J.
所有的变量都已经失效了。你正在返回一个悬空的 StreamReader,底层的 Stream 已经被关闭了。 - Ben Voigt
@Ben:是的,我刚注意到。回到我的原始建议...在原始的使用块中使用sr。 - Eric J.

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