4

I found a strange problem with FileStream.SetLength(0). When writing first something to a stream and then calling SetLength(0), the content of the previous write still gets written to the file:

var fileName = "test.txt";
using (var fileStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 8000, FileOptions.None)) 
{
    using (var streamWriter = new StreamWriter(fileStream, Encoding.Default, bufferSize: 8000, leaveOpen: true)) 
    {
        streamWriter.WriteLine("123");
        fileStream.SetLength(0);
        streamWriter.WriteLine("abc");
    }
}
  var fileContent = File.ReadAllText(fileName);

fileContent becomes "123\r\nabc\r\n"

Obviously, 123 did not get deleted, even it was written before calling SetLength(0).

Using Seek() or setting the Position to 0 did not help.

In my application, I am writing to a file, which I keep open. From time to time it can happen that I need to empty the file completely and write a different content. For performance reasons, I don't want to close the streamWriter.

Note to all those guys who love to mark wrongly questions as duplicates

I made the very frustrating experience on Stackoverflow that a question got marked wrongly as duplicate. I spent a day writing the question, he decides in an instant that it's a duplicate, even when it isn't. This usually happens when that guy doesn't truly understand the question and doesn't bother to find the correct answer. He just feels that a similar problem was solved before on SO. But details matter. Yes, there are questions about truncating a file using SetLength(0), but they are different in one crucial point: In those examples, SetLength(0) is not preceded by a Write(). But this is essential, without it there is no problem.

Please, please, be very careful and considerate when blocking others from answering by marking a question as duplicate.

Peter Huber
  • 3,052
  • 2
  • 30
  • 42

1 Answers1

3

When inspecting FileStream.SetLength() on referencesource.microsoft.com, I noticed this funny looking piece of code:

        // Handle buffering updates.
        if (_writePos > 0) {
            FlushWrite(false);
        }

It checks within SetLength() if the internal FileStream buffer has still some write content and writes it first to the file, before executing the rest of SetLength().

A very strange behavior. I could solve this problem by emptying the internal buffer before calling SetLength() using a Flush() first:

  var fileName = "test.txt";
  using (var fileStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 8000, FileOptions.None)) {
    using (var streamWriter = new StreamWriter(fileStream, Encoding.Default, bufferSize: 8000, leaveOpen: true)) {
      streamWriter.WriteLine("123");
      streamWriter.Flush();
      fileStream.SetLength(0);
      streamWriter.WriteLine("abc");
    }
  }
  var fileContent = File.ReadAllText(fileName);

fileContent is now: "abc\r\n"

I would be interested to hear if also other feel this is a bug in FileStream ?

Peter Huber
  • 3,052
  • 2
  • 30
  • 42
  • Does not look like a bug. FileStream's internal buffer is just transparent for you and the code above will help to avoid bug (otherwise you would have to call fileStream.Flush explicitly before calling fileStream.SetLength). As @hans-passant said: Your "problem" is StreamWriter did not flush its internal (text) buffer to fileStream. Compare AutoFlush https://learn.microsoft.com/de-de/dotnet/api/system.io.streamwriter.autoflush – stb Nov 15 '22 at 10:54
  • Well, the "bug" is this: I did not want that anything more gets written to the file, because I wanted to start again with an empty file. That fileStream.SetLength(0) is not setting the stream length to 0 is strange. It is also strange that I have to force with flush that everything gets written to the file, even I don't want this to be written to the file. – Peter Huber Nov 15 '22 at 15:42
  • SetLength(0) works, but if you write more data after that call the file won't be empty. More data is written by everything after SetLength including pending internal buffer flushing of your StreamWriter (caused by Flush/Dispose/Close or more Write calls). Enabling Autoflush or calling streamwriter.Flush before SetLength will help. – stb Nov 15 '22 at 20:32
  • Yes, flushing before setting the file length to 0 is the only way to make this work, as I explained in my answer. But that is a strange requirement, because I don't want the data to be written to the file, if that has not happened yet. Imagine if you set a StringBuilder Length to 0 and then it still has some old content. Or empty a List or ... I understand that a FileStream is different, it stores some data in RAM before it gets written to the file. But if I tell the FileStream I want it to have the length 0, which should be the same like emptying it, there should no old content be left. – Peter Huber Nov 16 '22 at 00:15
  • You would get same result with a StreamWriter that writes to a Stringbuilder (if that would be compatible, e.g. by using a proxy class). – stb Nov 16 '22 at 06:45