C# 为什么处理StreamReader会使流不可读?

C# 为什么处理StreamReader会使流不可读?,c#,stream,idisposable,streamreader,C#,Stream,Idisposable,Streamreader,我需要把一条流从头到尾读两遍 但是下面的代码抛出了一个ObjectDisposedException:无法访问已关闭的文件异常 string fileToReadPath = @"<path here>"; using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open)) { using (StreamReader reader = new StreamReader(fs)) {

我需要把一条流从头到尾读两遍

但是下面的代码抛出了一个
ObjectDisposedException:无法访问已关闭的文件
异常

string fileToReadPath = @"<path here>";
using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open))
{
    using (StreamReader reader = new StreamReader(fs))
    {
        string text = reader.ReadToEnd();
        Console.WriteLine(text);
    }

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException thrown.

    using (StreamReader reader = new StreamReader(fs))
    {
        string text = reader.ReadToEnd();
        Console.WriteLine(text);
    }
}
字符串fileToReadPath=@”;
使用(FileStream fs=newfilestream(fileToReadPath,FileMode.Open))
{
使用(StreamReader=新StreamReader(fs))
{
string text=reader.ReadToEnd();
控制台写入线(文本);
}
Seek(0,SeekOrigin.Begin);//抛出ObjectDisposedException。
使用(StreamReader=新StreamReader(fs))
{
string text=reader.ReadToEnd();
控制台写入线(文本);
}
}

为什么会这样?到底是什么?为什么操纵
StreamReader
会以这种方式影响关联的流?期望一个可查找的流可以被多次读取,包括由多个
StreamReader
s读取,这难道不合乎逻辑吗?

Dispose()的目的是在处理完流后清理资源。读卡器影响流的原因是因为读卡器只是过滤流,因此处理读卡器没有任何意义,除非它将调用链接到源流

要修复代码,只需始终使用一个读卡器:

using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open))
using (StreamReader reader = new StreamReader(fs))
{
    string text = reader.ReadToEnd();
    Console.WriteLine(text);

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException not thrown now

    text = reader.ReadToEnd();
    Console.WriteLine(text);
}
编辑以处理以下评论

在大多数情况下,您不需要像在代码中那样访问底层流(
fs.Seek
)。在这些情况下,
StreamReader
将其调用链接到底层流的事实允许您完全不使用流的
usings
语句来节省代码。例如,代码如下所示:

using (StreamReader reader = new StreamReader(new FileStream(fileToReadPath, FileMode.Open)))
{
    ...
}

Using
定义了一个作用域,对象将在该作用域之外处置,因此
ObjectDisposedException
。您无法访问此块之外的StreamReader内容

发生这种情况是因为
StreamReader
接管了流的“所有权”。换句话说,它使自己负责关闭源流。只要您的程序调用
Dispose
Close
(在您的情况下使用
语句范围保留
),那么它也将处理源流。在您的情况下调用fs.Dispose()
。因此,使用
块离开第一个
后,文件流就死了。这是一致的行为,在.NET中包装另一个流的所有流类的行为都是这样的

StreamReader
有一个构造函数,允许声明它不拥有源流。但是,它不能从.NET程序访问,构造函数是内部的


在这种特殊情况下,您可以通过不对
StreamReader
使用
using
-语句来解决问题。然而,这是一个相当复杂的实现细节。当然有更好的解决方案可供您使用,但代码过于合成,无法提出真正的解决方案。

Dispose()。不幸的是,streams没有
Detach()
方法,因此您必须在这里创建一些变通方法。

我不知道为什么,但您可以不使用StreamReader。这样,即使收集了StreamReader,您的底层流也不会被处理。

我同意您的问题。这种有意产生的副作用的最大问题是,开发人员不知道它,并且盲目地遵循“最佳实践”,即使用
围绕StreamReader。但当它位于长寿命对象的属性上时,它可能会导致一些真正难以追踪的bug,我见过的最好(最差?)的例子是

using (var sr = new StreamReader(HttpContext.Current.Request.InputStream))
{
    body = sr.ReadToEnd();
}
开发人员不知道InputStream现在被用于任何将来希望它出现的地方

显然,一旦您了解了内部结构,就可以使用
避免
,只需读取并重置位置。但我认为API设计的一个核心原则是避免副作用,尤其是不要破坏您正在处理的数据。假定是“读取器”的类的任何固有特性都不应在“使用”它时清除它读取的数据。处理读取器应该释放对流的任何引用,而不是清除流本身。我能想到的唯一一件事是,必须做出选择,因为读者正在改变流的其他内部状态,比如seek指针的位置,他们假设如果你在它周围包装一个using,那么你将有意地完成所有事情。另一方面,就像在您的示例中一样,如果您正在创建一个流,则流本身将使用
处于一个
中,但是如果您正在读取一个在立即方法之外创建的流,则代码会擅自清除数据

我所做的,并告诉我们的开发人员做的流实例,阅读代码没有显式创建是

// save position before reading
long position = theStream.Position;
theStream.Seek(0, SeekOrigin.Begin);
// DO NOT put this StreamReader in a using, StreamReader.Dispose() clears the stream
StreamReader sr = new StreamReader(theStream);
string content = sr.ReadToEnd();
theStream.Seek(position, SeekOrigin.Begin);

(对不起,我把它作为一个答案,不适合评论,我想更多的讨论这个框架的设计决定)

在这样的情况下也考虑使用Soal.Io.Frame.RealLabTeX()。更简单。@Dave Markle:你说得对。我把它当作一个简短的例子。事实上,在实际代码中,我处理的流可能非常大,因此第一个读卡器每行读取一行,然后,该流每字节复制到另一个流中。事实上,我在问为什么读卡器上的Dispose()会影响流。我不明白你的答案。这是否意味着readers Dispose仅用于清理流数据?那么为什么即使是
StreamReader
实现了
IDisposable
,如果它没有做任何有用的事情(因为处理流本身在任何情况下都会完成所有工作)?@MainMa,Dispose()的目的始终是确保与给定的
IDisposable
相关的所有资源都被清除。因为读者和流代表了