BackwardFileReader - VB.Net and C# Source


Update

Since the original writing of this article in 2010 I have cleaned the code up somewhat and provided the source on GitHub as well as a Nuget package that can be used in both classic the class Framework and all .NET core versions.

I had a need to read through a file backwards today, but not wanting to read the entire file into an array (in case the file is very large) I wanted to use a stream and read line by line. I couldn’t find what I needed inside of the Framework, so I searched on the web and came across a posting on the MSDN forum by user named “the_real_herminator”. I tried his C# code out, like it and went ahead and converted it to VB.Net (and made a few small changes). Below is a link to the MSDN thread where the source originated and also the source of what I created. I’m going to go ahead and include the source code in C# that was originally posted also in case the MSDN thread moves in the future (we all know how that goes in this day and age). Hope this helps someone else. ;)

http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/9acdde1a-03cd-4018-9f87-6e201d8f5d09

The formatting of these may cut off in this small columned template, copy and paste it into whatever text editor or source editor you use and it should come out fine.

VB.Net

Imports System.IO
Imports System.Text

''' <summary>
''' Class to read a text file backwards.
''' </summary>
''' <remarks></remarks>
Public Class BackwardReader
    Implements IDisposable
    '*********************************************************************************************************************************
    '
    '             Class:  BackwardReader
    '      Initial Date:  11/29/2010
    '     Last Modified:  11/29/2010
    '     Programmer(s):  Original C# Source - the_real_herminator
    '                     http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/9acdde1a-03cd-4018-9f87-6e201d8f5d09
    '                     VB Converstion - Blake Pell
    '
    '*********************************************************************************************************************************

    Private _path As String = ""
    Private _fs As FileStream = Nothing
    ''' <summary>
    ''' Constructor
    ''' </summary>
    ''' <param name="path"></param>
    Public Sub New(ByVal path As String)
        _path = path
        _fs = New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
        _fs.Seek(0, SeekOrigin.[End])
    End Sub

    ''' <summary>
    ''' Read's the next line in.
    ''' </summary>
    Public Function ReadLine() As String
        Dim line As Byte()
        Dim text As Byte() = New Byte(0) {}
        Dim position As Long = 0
        Dim count As Integer = 0
        _fs.Seek(0, SeekOrigin.Current)
        position = _fs.Position

        'do we have trailing rn?
        If _fs.Length > 1 Then
            Dim vagnretur As Byte() = New Byte(1) {}
            _fs.Seek(-2, SeekOrigin.Current)
            _fs.Read(vagnretur, 0, 2)

            If ASCIIEncoding.ASCII.GetString(vagnretur).Equals(vbCr & vbLf) Then
                'move it back
                _fs.Seek(-2, SeekOrigin.Current)
                position = _fs.Position
            End If
        End If

        While _fs.Position > 0
            text.Initialize()

            'read one char
            _fs.Read(text, 0, 1)

            Dim asciiText As String = ASCIIEncoding.ASCII.GetString(text)
            'moveback to the charachter before
            _fs.Seek(-2, SeekOrigin.Current)

            If asciiText.Equals(vbLf) Then
                _fs.Read(text, 0, 1)
                asciiText = ASCIIEncoding.ASCII.GetString(text)
                If asciiText.Equals(vbCr) Then
                    _fs.Seek(1, SeekOrigin.Current)
                    Exit While
                End If
            End If
        End While

        count = Integer.Parse((position - _fs.Position).ToString())
        line = New Byte(count - 1) {}
        _fs.Read(line, 0, count)
        _fs.Seek(-count, SeekOrigin.Current)
        Return ASCIIEncoding.ASCII.GetString(line)
    End Function

    ''' <summary>
    ''' Whether or not the start of file has been reached.
    ''' </summary>
    Public ReadOnly Property SOF() As Boolean
        Get
            Return _fs.Position = 0
        End Get
    End Property

    ''' <summary>
    ''' Closes the FileStream and disposes of it's resources.
    ''' </summary>
    Public Sub Close()
        _fs.Close() : _fs.Dispose()
    End Sub

    Private disposedValue As Boolean = False        ' To detect redundant calls
    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: free other state (managed objects).
                Me.Close()
            End If
            ' TODO: free your own state (unmanaged objects).
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

C#

using System.Text;
using System.IO;
public class BackwardReader
{
    private string path;
    private FileStream fs = null;

    public BackwardReader(string path)
    {
        this.path = path;
        fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        fs.Seek(0, SeekOrigin.End);
    }

    public string Readline()
    {
        byte[] line;
        byte[] text = new byte[1];
        long position = 0;
        int count;
        fs.Seek(0, SeekOrigin.Current);
        position = fs.Position;

        //do we have trailing rn?
        if (fs.Length > 1)
        {
            byte[] vagnretur = new byte[2];
            fs.Seek(-2, SeekOrigin.Current);
            fs.Read(vagnretur, 0, 2);

            if (ASCIIEncoding.ASCII.GetString(vagnretur).Equals("rn"))
            {
                //move it back
                fs.Seek(-2, SeekOrigin.Current);
                position = fs.Position;
            }
        }

        while (fs.Position > 0)
        {
            text.Initialize();

            //read one char
            fs.Read(text, 0, 1);

            string asciiText = ASCIIEncoding.ASCII.GetString(text);

            //moveback to the charachter before
            fs.Seek(-2, SeekOrigin.Current);

            if (asciiText.Equals("\n"))
            {
                fs.Read(text, 0, 1);
                asciiText = ASCIIEncoding.ASCII.GetString(text);
                if (asciiText.Equals("\r"))
                {
                    fs.Seek(1, SeekOrigin.Current);
                    break;
                }
            }
        }

        count = int.Parse((position - fs.Position).ToString());
        line = new byte[count];
        fs.Read(line, 0, count);
        fs.Seek(-count, SeekOrigin.Current);
        return ASCIIEncoding.ASCII.GetString(line);
    }

    public bool SOF
    {
        get
        {
            return fs.Position == 0;
        }
    }

    public void Close()
    {
        fs.Close();
    }
}