views:

103

answers:

5

Hi,

I need to read a stream two times, from start to end.

But the following code throws an ObjectDisposedException: Cannot access a closed file exception.

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);
    }
}

Why is it happening? What is really disposed? And why manipulating StreamReader affects the associated stream in this way? Isn't it logical to expect that a seekable stream can be read several times, including by several StreamReaders?

+5  A: 

The purpose of Dispose() is to clean up resources when you're finished with the stream. The reason the reader impacts the stream is because the reader is just filtering the stream, and so disposing the reader has no meaning except in the context of it chaining the call to the source stream as well.

To fix your code, just use one reader the entire time:

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);
}

Edited to address comments below:

In most situations, you do not need to access the underlying stream as you do in your code (fs.Seek). In these cases, the fact that StreamReader chains its call to the underlying stream allows you to economize on the code by not using a usings statement for the stream at all. For example, the code would look like:

using (StreamReader reader = new StreamReader(new FileStream(fileToReadPath, FileMode.Open)))
{
    ...
}
Kirk Woll
In fact, I'm asking why Dispose() on the *reader* affects the *stream*. I don't understand your answer. Does it mean that readers Dispose has only a purpose to clean the stream data? So why even `StreamReader` implements `IDisposable`, if it doesn't do anything *useful* (since disposing the stream itself will in all cases do all the work)?
MainMa
@MainMa, the purpose of Dispose() is *always* to make sure all resources associated with the given `IDisposable` are cleaned up. Since the reader and the stream represent the same entity disposing of one has the same effect as disposing the other.
Kirk Woll
+1  A: 

Using defines a scope, outside of which an object will be disposed, thus the ObjectDisposedException. You can't access the StreamReader's contents outside of this block.

Tyler
+4  A: 

This happens because the StreamReader takes over 'ownership' of the stream. In other words, it makes itself responsible for closing the source stream. As soon as your program calls Dispose or Close (leaving the using statement scope in your case) then it will dispose the source stream as well. Calling fs.Dispose() in your case. So the file stream is dead after leaving the first using block.

There is one constructor for StreamReader that allows saying that it doesn't own the source stream. It is however not accessible from a .NET program, the constructor is internal.

In this particular case, you'd solve the problem by not using the using statements for the StreamReader. That's however a fairly hairy implementation detail. There's surely a better solution available to you but the code is too synthetic to propose a real one.

Hans Passant
After a lot of hesitation, I accept this answer over the one by @Kirk Woll, since I find this one more clear. Both are still great answers to my question. Thanks.
MainMa
A: 

Dispose() on parent will Dispose() all owned streams. Unfortunately, streams don't have Detach() method, so you have to create some workaround here.

Daniel Mošmondor
A: 

I don't know why, but you can leave your StreamReader undisposed. That way your underlying stream won't be disposed, even when StreamReader got collected.

tia
@tia: Of course. But it's not too intuitive to not put a using to a `StreamReader` for me, and it will probably violate FxCop rules. By the way, the issue at the origin of this question appeared when I refactored and old code where usings were completely missing.
MainMa