Return StreamReader to Beginning
Categories:
Mastering StreamReader: How to Rewind and Re-read Your Data Streams

Learn essential techniques in C# .NET to reset a StreamReader to its beginning, enabling multiple passes over stream data for various processing needs.
When working with StreamReader
in C# .NET, you often encounter scenarios where you need to process the same stream of data multiple times. Unlike some other stream types, StreamReader
itself doesn't inherently provide a direct 'rewind' method. This article explores various strategies to effectively return a StreamReader
to its beginning, allowing you to re-read its content from the start. We'll cover common pitfalls, best practices, and demonstrate solutions using practical code examples.
Understanding StreamReader and Stream Positions
A StreamReader
is designed for sequential reading of characters from a stream. It maintains an internal buffer and a position within the underlying stream. Once you read data, the stream's position advances. The key to 'rewinding' a StreamReader
lies in manipulating the position of its underlying stream. Not all streams support seeking (changing their position). For instance, network streams or console input streams are typically forward-only. File streams and MemoryStream
objects, however, are seekable.
CanSeek
property before attempting to reset its position. Attempting to seek on a non-seekable stream will result in a NotSupportedException
.Method 1: Resetting the Underlying Stream's Position
The most common and direct way to 'rewind' a StreamReader
is to reset the position of the Stream
it wraps. This involves setting the Position
property of the underlying stream back to 0
. After resetting the stream's position, you must also clear the StreamReader
's internal buffer to ensure it re-reads from the new stream position rather than serving buffered data from the previous read.
using System;
using System.IO;
using System.Text;
public class StreamReaderRewindExample
{
public static void Main(string[] args)
{
string data = "Line 1\nLine 2\nLine 3";
// Create a MemoryStream from the string data
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(data)))
{
using (StreamReader reader = new StreamReader(ms))
{
// First read
Console.WriteLine("--- First Read ---");
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
// Check if stream can be seeked
if (ms.CanSeek)
{
// Reset the underlying stream's position to the beginning
ms.Position = 0;
// Clear the StreamReader's internal buffer
reader.DiscardBufferedData();
// Second read
Console.WriteLine("\n--- Second Read ---");
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
else
{
Console.WriteLine("\nUnderlying stream does not support seeking.");
}
}
}
}
}
Example of resetting a StreamReader using the underlying stream's Position and DiscardBufferedData().

Workflow for resetting a StreamReader's position.
Method 2: Recreating the StreamReader
If the underlying stream is seekable, but you prefer a simpler approach or encounter issues with DiscardBufferedData()
, you can always dispose of the existing StreamReader
and create a new one, passing the same underlying stream (after resetting its position). This guarantees a fresh state for the reader.
using System;
using System.IO;
using System.Text;
public class RecreateStreamReaderExample
{
public static void Main(string[] args)
{
string data = "Item A\nItem B\nItem C";
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(data)))
{
// First read
Console.WriteLine("--- First Read (Original Reader) ---");
using (StreamReader reader1 = new StreamReader(ms))
{
string line;
while ((line = reader1.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
// Check if stream can be seeked
if (ms.CanSeek)
{
// Reset the underlying stream's position
ms.Position = 0;
// Create a new StreamReader for the second read
Console.WriteLine("\n--- Second Read (New Reader) ---");
using (StreamReader reader2 = new StreamReader(ms))
{
string line;
while ((line = reader2.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
else
{
Console.WriteLine("\nUnderlying stream does not support seeking.");
}
}
}
}
Recreating a StreamReader after resetting the underlying stream's position.
Method 3: Caching Stream Content (for non-seekable streams)
What if your Stream
is not seekable (e.g., a network stream, Console.In
)? In such cases, you cannot reset its position. The solution is to read the entire content of the stream into a seekable stream (like MemoryStream
) or a string/byte array first. Then, you can create multiple StreamReader
instances from this cached content.
using System;
using System.IO;
using System.Text;
public class CacheStreamReaderExample
{
public static void Main(string[] args)
{
// Simulate a non-seekable stream (e.g., from network or console)
// For demonstration, we'll use a MemoryStream but treat it as non-seekable
// by not directly manipulating its Position for the initial read.
string originalData = "Data for processing 1\nData for processing 2\nData for processing 3";
Stream nonSeekableSource = new MemoryStream(Encoding.UTF8.GetBytes(originalData));
MemoryStream cachedStream = new MemoryStream();
nonSeekableSource.CopyTo(cachedStream);
cachedStream.Position = 0; // Rewind the cached stream
// First read from the cached stream
Console.WriteLine("--- First Read from Cached Stream ---");
using (StreamReader reader1 = new StreamReader(cachedStream, Encoding.UTF8, leaveOpen: true))
{
string line;
while ((line = reader1.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
// Reset the cached stream's position for the second read
cachedStream.Position = 0;
// Second read from the cached stream
Console.WriteLine("\n--- Second Read from Cached Stream ---");
using (StreamReader reader2 = new StreamReader(cachedStream, Encoding.UTF8, leaveOpen: true))
{
string line;
while ((line = reader2.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
// Clean up
nonSeekableSource.Dispose();
cachedStream.Dispose();
}
}
Caching stream content into a MemoryStream for multiple reads.