-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
In below snippet I was expecting an exception to be thrown but instead the StreamWriter keeps working:
var ms = new MemoryStream();
var sw = new StreamWriter(ms, leaveOpen: true);
sw.Write("Hello");
sw.Dispose();
sw.Write(" World");
sw.Flush();
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray())); // prints "Hello World".If I change leaveOpen to false, it does throw as I expected.
Originally posted by @jozkee in #89477 (comment)
[Breaking Change] StreamWriter keeps working after being disposed if leaveOpen: true is used
We are fixing StreamWriter so that it correctly throws ObjectDisposedException after Dispose() / DisposeAsync() when constructed with leaveOpen: true. The fix is currently in PR #125189.
Related:
Goal
The goal is to align StreamWriter disposal behavior with StreamReader and standard IDisposable semantics. When leaveOpen: true, Dispose() should mark the writer as disposed (so subsequent operations throw) while leaving the underlying stream open. Currently, the _disposed flag is never set when leaveOpen: true, which means write and flush calls silently succeed after disposal.
Existing Behavior
When a StreamWriter is constructed with leaveOpen: true and then disposed, subsequent Write, Flush, and async variants continue to work without throwing any exception:
var ms = new MemoryStream();
var sw = new StreamWriter(ms, leaveOpen: true);
sw.Write("Hello");
sw.Dispose();
sw.Write(" World"); // No exception — writes succeed
sw.Flush();
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
// prints "Hello World"Note: StreamReader with the same leaveOpen: true flag correctly throws ObjectDisposedException after disposal. This inconsistency has existed since the leaveOpen parameter was introduced.
Breaking Change
After this fix, all write and flush operations on a disposed StreamWriter throw ObjectDisposedException, regardless of the leaveOpen parameter:
var ms = new MemoryStream();
var sw = new StreamWriter(ms, leaveOpen: true);
sw.Write("Hello");
sw.Dispose();
sw.Write(" World"); // Now throws ObjectDisposedExceptionThe change is technically breaking because code that relied on the ability to write after disposal with leaveOpen: true will now throw at runtime. However, use-after-dispose is always documented as erroneous behavior in .NET, so we expect the practical impact to be low.
Error: System.ObjectDisposedException: Cannot write to a closed TextWriter.
Workaround: Remove usage patterns that write to a StreamWriter after calling Dispose() or DisposeAsync(). If you need to continue writing to the underlying stream, create a new StreamWriter wrapping it.
Affected APIs: All overloads of StreamWriter.Write, StreamWriter.WriteLine, StreamWriter.WriteAsync, StreamWriter.WriteLineAsync, StreamWriter.Flush, and StreamWriter.FlushAsync.
Feedback
We would love your feedback on this change. If you have scenarios where you depend on the current (pre-fix) behavior, please comment on this issue.