Search code examples
c#streamfilestreamioexception

How do you cause a filestream to re-read a line in the event of an exception?


I have a filestream being read by a streamreader. I want to return the position of the stream to the previous line in the event of an IOException to try and read the same line again. I tried to just record the position of the stream before the line, and then seek to that point, but I must be misunderstanding or misusing it.

using (var fs = new FileStream("MyPath\\linetest.txt", FileMode.Open))
using (var sr = new StreamReader(fs))
{
    Console.WriteLine(fs.CanSeek);
    while (!sr.EndOfStream)
    {
        string line;
        try
        {
            var streamPosition = fs.Position;
            line = sr.ReadLine();
            if (line.StartsWith("#"))
            {
                fs.Seek(streamPosition, SeekOrigin.Begin);
                throw new IOException();
            }
            Console.WriteLine(line);
        }
        catch (IOException e)
        {
            Console.WriteLine(e.Message);
        }
    }

In case its not obvious, I'm throwing an exception when a line is read that starts with "#". only one line in my 7 line test file starts with #, so I would expect that line to be written in an infinite loop, however, when I execute this, it prints each line of the file, substituting the exception message for the line that starts with #. Any insight or help you guy and gals could offer would be appreciated!


Solution

  • The reason why your code goes through to the end of input file is that the position you take by fs.Position is the one up to which StreamReader reads from FileStream into StreamReader's buffer at that phase (Position A). This is different from the position up to which StreamReader has returned lines by sr.ReadLine() (Position B). Off course, Position A is always ahead of Position B.

    StreamReader does not have a way to provide Position B. It is not impossible to acquire such physical position by accumulatively calculating from what ReadLine() returns every time, but think of a variety of encoding and EOL and BOM code. That'll be not easy to be accurate.

    Instead, why don't you get logical position after the decode by StreamReader? I mean, keep the line number in this case.

    var row = 0;
    using (var fs = new FileStream("input/sample.txt", FileMode.Open)
    using (var sr = new StreamReader(fs))
    {
        Console.WriteLine(fs.CanSeek);
        while (!sr.EndOfStream)
        {
            try
            {
                var line = sr.ReadLine();
                if (line.StartsWith("#"))
                    throw new IOException();
                row++;
                Console.WriteLine(line);
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message);
    
                // Reset to the line where the exception happened
                fs.Seek(0, SeekOrigin.Begin);
                sr.DiscardBufferedData();
                for (var i = 0; i < row; i++)
                    sr.ReadLine();
            }
        }
    }
    

    sr.DiscardBufferedData() in the catch block is for clearing up the buffer in StreamReader that I mentioned at the beginning. Keep it in mind that sr.DiscardBufferedData() slows performance, which is mentioned in here.

    I don't get why you want to keep the file stream open and keep getting the same error result. Be aware that the program never get updates on the file while the stream is open. Therefore, I leave one more code, which can keep reading the same line until the line is corrected, provided that already-processed lines are never edited in terms of line break.

    var row = 0;
    while (true)
    {
        using (var fs = new FileStream("input/sample.txt", FileMode.Open))
        using (var sr = new StreamReader(fs))
        {
            for (var i = 0; i < row; i++)
                sr.ReadLine();
    
            try
            {
                while (!sr.EndOfStream)
                {
                    var line = sr.ReadLine();
                    if (line.StartsWith("#"))
                        throw new IOException();
                    row++;
                    Console.WriteLine(line);
                }
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message);
            }
            if (sr.EndOfStream)
                break;
        }
    }
    

    However, in either way, repeatedly reading through from the beginning to the error line is not so good even if you concern performance. In that case, you should reconsider the design how you input data.