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. ;)
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();
}
}