2

The goal is to be able to close the stream and return to the same position (as a "bookmark").

Edit: two users suggested that I had to use streamReader.DiscardBufferedData(), and that at least made Test1 and Test2 react the same way. I've erased Test2 (for info: it was the exact same code as Test1, but closing and re-opening the stream), and "repaired" Test1:

Test1 still fails:

public static void Test1(string filePath)
{
    var line1 = default(string);
    var line2 = default(string);
    var line3 = default(string);

    using (StreamReader streamReader = File.OpenText(filePath))
    {
        // read 2 consecutive lines:
        line1 = streamReader.ReadLine();
        var line2StreamPosition = streamReader.BaseStream.Position; // save position at the begining of line2:
        line2 = streamReader.ReadLine();

        // go back to the position of line2 and try to re-read it:
        streamReader.DiscardBufferedData();
        streamReader.BaseStream.Seek(line2StreamPosition, SeekOrigin.Begin);
        line3 = streamReader.ReadLine();

        if (line2 == line3)
        {
            // it doesn't reach this point
        }
        else
        {
            // it fails ...
            // it starts reading `line3` at an incorrect position
        }

    }
}

Also: var canSeek = streamReader.BaseStream.CanSeek; // true.

Xavier Peña
  • 7,399
  • 9
  • 57
  • 99
  • 1
    I think this is the same issue as [here](https://stackoverflow.com/questions/2053206/return-streamreader-to-beginning/2053222#2053222). It isn't enough to set the position. You also need to tell the stream to discard its buffer. –  May 09 '17 at 14:34
  • @Amy Thanks, that solves the issue for `Test1`. Now both tests react the same way (but they both fail). – Xavier Peña May 09 '17 at 14:37
  • 1
    Call [`StreamReader.DiscardBufferedData()`](https://msdn.microsoft.com/en-us/library/system.io.streamreader.discardbuffereddata(v=vs.110).aspx) – Matthew Watson May 09 '17 at 14:37
  • 1
    What you did cannot work reliably. Read the [MSDN documentation](https://msdn.microsoft.com/en-us/library/yhfzs7at(v=vs.110).aspx) for the `StreamReader(Stream stream)` constructor, especially the the Remarks section. Do you notice the bit about the internal buffer? To put briefly: When you read a line from the StreamReader, the reader does not only just read the bytes of the lines from the underlying stream, but reads as many bytes as necessary to fill the internal buffer. Hence, line2StreamPosition is not necessarily the position of the second string/line in the stream. –  May 09 '17 at 14:50
  • 1
    Instead of manipulating the underlying stream, you could create your own StreamReader class (if possible), which you can expand to allow putting strings/lines back into the reader... –  May 09 '17 at 14:53
  • @elgonzo "When you read a line from the StreamReader, the reader does not only just read the bytes of the lines from the underlying stream, but reads as many bytes as necessary to fill the internal buffer." <= that makes sense. Thanks. From your hint I understand that there is no way to use the "out of the box" StreamReader and achieve what I am trying to achieve (which is targeting the exact byte position at the start of the next line). – Xavier Peña May 09 '17 at 14:58
  • Is it an option to use `ReadAllLines` and perform the positioning logic on the returned array of string-lines instead? – Olivier Jacot-Descombes May 09 '17 at 14:59
  • Yeah, unfortunately you cannot set the internal buffer of StreamReader to a size of zero, as the StreamReader adjusts the minimum size of the internal buffer to 128 bytes :( –  May 09 '17 at 14:59
  • @OlivierJacot-Descombes Unfortunately my file is way too big to use `ReadAllLines`. – Xavier Peña May 09 '17 at 15:01
  • @elgonzo I see... I've found that others suggest [the same solution](http://stackoverflow.com/a/10190359/831138). In that case, a simpler solution for me would be: since my program doesn't really care if one or multiple lines are repeated, I can try to go back "N=BufferSize positions" and discard the first line (which will likely be corrupted). On paper I think it makes sense, I'll see if still stands after implementing it. – Xavier Peña May 09 '17 at 15:05
  • Oh one more note about my second comment: Do not expand StreamReader as i said (stupid suggestion), but rather derivee your own WrapperReader from System.IO.Reader. Pass the StreamReader to this WrapperReader and let the WrapperReader read from it. The WrapperReader then also implements the put-strings/lines-back logic... –  May 09 '17 at 15:05

1 Answers1

1

You could compute the bookmark position with the corresponding encoding but the problem here is that there is no easy way to figure out what the characters that make up the line break are; it doesn't necessarily have to be Environment.NewLine.

If you do know beforehand, what characters make up the linebreak in your environmet, you could do the following:

var line2StreamPosition = streamReader.CurrentEncoding
                                      .GetByteCount(
                                           string.Concat(line1, myNewLine)
                                                 .ToArray());
InBetween
  • 32,319
  • 3
  • 50
  • 90
  • What happened to the property being null (and not being null) question you asked yesterday? You figured it out? – dragonfly02 May 11 '17 at 10:18
  • @stt106 There is definitely a bug in how VS loads up locals while debugging but the assertion was failing due to another reason. It wasn't due to the null reference exception as I mistakenly thought. That changes the wole set up of the question so I decided to delete it. I can live with the debugging bug, what was really bewildering was an assertion failing due to that bug, and that is not the case. – InBetween May 11 '17 at 10:42