64

Is there a way to do this:

this.logFile = File.Open("what_r_u_doing.log", FileMode.OpenOrCreate, FileAccess.ReadWrite);

using(var sr = new StreamReader(this.logFile))
{
    // Read the data in
}

// ... later on in the class ...

this.logFile = File.Open("what_r_u_doing.log", FileMode.OpenOrCreate, FileAccess.ReadWrite);

using(var sw = new StreamWriter(this.logFile))
{
    // Write additional data out...
}

Without having to open the file twice?

I can't seem to make the StreamReader not-dispose my stream. I don't want to just let it go out of scope, either. Then the garbage collector will eventually call the Dispose, killing the stream.

John Gietzen
  • 48,783
  • 32
  • 145
  • 190

9 Answers9

87

.NET 4.5 will finally fix this problem with a new constructors on StreamReader and StreamWriter that take a leaveOpen parameter:

StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)

StreamWriter(Stream stream, System.Text.Encoding encoding, int bufferSize, bool leaveOpen)
Simon Buchan
  • 12,707
  • 2
  • 48
  • 55
  • 27
    The problem with this is that it forces me to figure out what encoding to pass in to make the StreamReader behave the same way as it did when I opened it using the `StreamReader(Stream)` constructor. So I am still using the "don't dispose and hope none of my colleagues 'correct' the method by putting the StreamReader in a using block" approach :( – phoog Dec 11 '12 at 23:38
  • 19
    @phoog: (catching up on my inbox) - MSDN should list defaults for forwarding-style overloads, for example `StreamReader(Stream)` says "This constructor initializes the encoding to UTF8Encoding, the BaseStream property using the stream parameter, and the internal buffer size to 1024 bytes." – Simon Buchan Apr 25 '13 at 22:15
  • 9
    @SimonBuchan But I shouldn’t have to hardcode these defaults in my program. The `StreamReader()` constructor should have been altered to take an options-style class parameter, like how `XmlReaderSettings` works. Then the defaults can be managed by the framework and new parameters added in a non-ABI-breaking and non-hardbaked way. – binki Nov 27 '15 at 02:48
  • 1
    @binki Or they could've just provided a constructor that takes a `Stream` and a `bool` =/ Nevermind, I see they already have a constructor with that signature. Sigh. – crush Jan 25 '17 at 18:01
  • 2
    using(var reader = StreamReader(OtherStream, System.Text.Encoding.UTF8, 1024, true)) { ... } if you want to stick with the defaults. *comment moved* – TamusJRoyce Oct 27 '17 at 20:32
  • Be nice if the stream itself would throw an ObjectDisposedException when trying to read from it a second time. That could save a person hours of debugging why the stream is just returning an empty string. – user7817808 Aug 16 '18 at 00:19
  • 2
    The source code for StreamReader is https://referencesource.microsoft.com/#mscorlib/system/io/streamreader.cs. You can use the Microsoft Defaults for the middle parameters: Encoding=UTF8, detectedEncodingFromByteOrderMarks=true, bufferSize=4096. Then set leaveOpen=true. – Jean Libera Nov 07 '18 at 17:54
  • @JeanLibera The default buffer size is 1024 not 4096. – Stefan Ossendorf Jan 24 '19 at 10:30
  • @StefanOssendorf That seems to depend a little. See: https://referencesource.microsoft.com/#mscorlib/system/io/streamreader.cs,48 – Deantwo Aug 30 '19 at 12:05
  • 2
    Using a named argument avoids the need to manually specify defaults: new StreamReader(stream, leaveOpen: true) – Phil Dennis Oct 15 '19 at 14:38
53

I don't want to just let it go out of scope, either. Then the garbage collector will eventually call the Dispose, killing the stream.

Garbage collector will call the Finalize method (destructor), not the Dispose method. The finalizer will call Dispose(false) which will not dispose the underlying stream. You should be OK by leaving the StreamReader go out of scope if you need to use the underlying stream directly. Just make sure you dispose the underlying stream manually when it's appropriate.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
17

You could use the NonClosingStreamWrapper class from Jon Skeet's MiscUtil library, it serves exactly that purpose

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Here is a [gist](https://gist.github.com/angelyordanov/8d5123d54d80c9db51e561ebc7a7aa90) of a .NET Standard 2.1 version of Jon Skeet's NonClosingStreamWrapper. – Angel Yordanov Oct 26 '21 at 11:31
3

You could create a new class which inherits from StreamReader and override the Close method; inside your Close method, call Dispose(false), which as Mehrdad pointed out, does not close the stream. Same applies to StreamWriter, of course.

However, it seems like a better solution would simply be to hold onto the StreamReader and StreamWriter instances as long as you may need them. If you're already planning to keep the stream open, you might as well keep a StreamReader and StreamWriter open also. If you use StreamWriter.Flush and Stream.Seek correctly, you should be able to make this work even when doing both reading and writing.

Aaron
  • 2,013
  • 16
  • 22
2

Just remove the using-Block. You don't have to Dispose() the StreamReader if you don't want to do Dispose() the stream, I think.

eflorico
  • 3,589
  • 2
  • 30
  • 41
  • 3
    But isn’t there the possibility that in the future `StreamReader` would be modified to hold some nonmanaged resource, start implementing a finalizer, and then when going out of scope randomly close your stream when the garbage collector finalizes it? Or are we *supposed* to assume that, since this doesn’t happen now, it never will? – binki Nov 27 '15 at 02:51
2

Use another constructor overload where you can specifu a "leaveOpen" parameter to "true"

HANiS
  • 530
  • 5
  • 9
1

I always use something like this: (it also uses the leaveOpen argument)

public static class StreamreaderExtensions
{
    public static StreamReader WrapInNonClosingStreamReader(this Stream file) => new StreamReader(file, Encoding.UTF8, true, 1024, true);
}

Usage:

using (var reader = file.WrapInNonClosingStreamReader())
{
     ....
}
Nick N.
  • 12,902
  • 7
  • 57
  • 75
  • 1
    Based on the reference source, this seems like a good answer. It is equivalent to calling the Stream parameterless constructor, but specifying leaveOpen as true. – Elliott Beach Aug 16 '20 at 20:14
1

I was able to use leaveOpen parameter without specifying all the constructor params (encoding or buffer size) like this:

using var streaReader = new StreamReader(stream, leaveOpen: true);
Jakub
  • 21
  • 4
0

Close it yourself in a try/finally clause when you're done with it.

var sr = new StreamReader();
try {
    //...code that uses sr
    //....etc
}
finally
{
    sr.Close();
}
Mike Atlas
  • 8,193
  • 4
  • 46
  • 62