Who owns wrapped streams (e.g. TextWriter) in .NET?
Asked Answered
C

2

9

I've recently encountered an error "ObjectDisposedException: Cannot access a closed Stream"

[ObjectDisposedException: Cannot access a closed Stream.]
    System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count) +10184402
    System.Security.Cryptography.CryptoStream.FlushFinalBlock() +114
    System.Security.Cryptography.CryptoStream.Dispose(Boolean disposing) +48

when using code following the format:

using (var stream = new MemoryStream())
{
    using (var hashStream = new CryptoStream(stream,
                                    new SHA256Managed(), CryptoStreamMode.Write))
    using (var writer = new TextWriter(hashStream))
    {
        writer.Write("something");
    }
    // ^-- Exception occurs on hashStream Dispose
    //     While naively I assumed that TextWriter.Dispose wouldn't touch the
    //     underlying stream(s).
    return stream.ToArray();
}

So the exception is caused because the Dispose of the TextWriter Disposes the Stream (hashStream) that is wrapped. My questions are thus:

  1. Does this convention applied (with default constructors/arguments) to all stream in .NET?

    Is there canon discussing this resource usage pattern? For instance, can it be assumed that the CryptoStream would have closed the MemoryStream? I know the answer, and there are other questions specifically about this, but I would like it addressed in terms of a design guideline if there is such.

  2. Where is such behavior documented?

    I can't find "ownerships" discussed in the TextWriter(stream) or CryptoStream constructors - surely I am just looking at the wrong location. (Update: apparently I fail at reading, as pointed out by itsme86 this is documented in the TextWriter constructor documentation.)

  3. What is the universal accepted method to write such code?

    That is, the underlying stream needs to be read (at the end of all operations, and thus still open) while all the nested streams should be closed/flushed completely - a simple CryptoStream.Flush is not sufficient, for instance.

Clemons answered 18/4, 2014 at 21:53 Comment(1)
On a related note, this is also the reason why Dispose should normally be implemented, so that multiple calls do no harm.Thracophrygian
L
3

After reading the using statement C# spec and looking around some of the implemented streams (memory, file, etc..) i see that the default behavior is to dispose the underlying streams when calling Dispose(). There are certain streams where you can explicitly state that you dont want to dispose of the underlying stream, like in DeflateStream:

public DeflateStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen)

leaveOpen Type: System.Boolean true to leave the stream object open after disposing the DeflateStream object; otherwise, false.

Of course, you might work around the disposal by not using the using statement, or perhaps implementing a WrapperStream class which wrap your stream and doesn't dispose the underlying stream.

Lev answered 18/4, 2014 at 22:56 Comment(1)
This does appear to be the "standard behavior" (in .NET 3.5 anyway). Also, part of my problem was using a TextWriter and not a StreamWriter. The StreamWriter, like DeflateStream, has a "leave open" argument.Clemons
L
5

This is specifically mentioned in the StreamWriter() documentation.

The StreamWriter object calls Dispose() on the provided Stream object when StreamWriter.Dispose is called.

Lam answered 18/4, 2014 at 22:13 Comment(3)
I can count the number of times I've taken advantage of this feature on 0 hands. Really wish they hadn't introduced this concept of stream ownership.Proper
.NET 4.x adds the ability to instruct a Writer or Reader to not dispose of the stream.Od
@Od Ahh, my "problem" (in terms of finding the constructor with such an option) is then using TextWriter and not the subclass StreamWriter.Clemons
L
3

After reading the using statement C# spec and looking around some of the implemented streams (memory, file, etc..) i see that the default behavior is to dispose the underlying streams when calling Dispose(). There are certain streams where you can explicitly state that you dont want to dispose of the underlying stream, like in DeflateStream:

public DeflateStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen)

leaveOpen Type: System.Boolean true to leave the stream object open after disposing the DeflateStream object; otherwise, false.

Of course, you might work around the disposal by not using the using statement, or perhaps implementing a WrapperStream class which wrap your stream and doesn't dispose the underlying stream.

Lev answered 18/4, 2014 at 22:56 Comment(1)
This does appear to be the "standard behavior" (in .NET 3.5 anyway). Also, part of my problem was using a TextWriter and not a StreamWriter. The StreamWriter, like DeflateStream, has a "leave open" argument.Clemons

© 2022 - 2024 — McMap. All rights reserved.