ZetCode

C# TextReader

last modified April 20, 2025

This tutorial explains how to use the TextReader class in C# to read text data from various sources. TextReader is an abstract class that provides methods for reading character streams.

The TextReader class serves as the base class for StreamReader and StringReader. It provides a convenient API for reading text data from different input sources.

TextReader is designed for reading character input rather than binary data. It handles text encoding automatically when used with its concrete implementations.

Basic TextReader Example

This example demonstrates reading text from a file using StreamReader, which inherits from TextReader. We read a text file line by line.

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Create a sample text file first
        File.WriteAllText("example.txt", "First line\nSecond line\nThird line");

        // Read using TextReader (StreamReader)
        using (TextReader reader = new StreamReader("example.txt"))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                Console.WriteLine(line);
            }
        }
    }
}

The example creates a text file with three lines, then reads it back using StreamReader. The ReadLine method returns null when the end of file is reached. This pattern is common for processing text files line by line.

The using statement ensures proper resource cleanup. The StreamReader automatically handles file encoding detection based on the file's byte order mark (BOM) if present.

Reading Entire File Content

TextReader provides methods to read the entire content at once. This example shows how to read a complete file into a string.

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Create a sample text file
        File.WriteAllText("document.txt", "This is the complete content\nof our text file.");

        // Read entire content
        using (TextReader reader = new StreamReader("document.txt"))
        {
            string content = reader.ReadToEnd();
            Console.WriteLine("File content:");
            Console.WriteLine(content);
        }
    }
}

The ReadToEnd method reads all characters from the current position to the end of the text stream. This is useful for small files where memory usage isn't a concern.

For large files, consider reading line by line or in chunks to avoid high memory consumption. The example shows the simplicity of reading complete file contents when appropriate.

Reading Characters Individually

TextReader allows reading single characters or blocks of characters. This example demonstrates character-by-character reading.

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        File.WriteAllText("chars.txt", "ABC");

        using (TextReader reader = new StreamReader("chars.txt"))
        {
            int charCode;
            while ((charCode = reader.Read()) != -1)
            {
                char c = (char)charCode;
                Console.WriteLine($"Read character: {c} ({(int)c})");
            }
        }
    }
}

The Read method returns the next character as an integer, or -1 at end of stream. We cast the integer to char for display. This low-level approach provides precise control over text processing.

Character-by-character reading is useful for parsing tasks or when processing text with specific formatting requirements. The example shows both the character and its Unicode code point.

Reading Character Blocks

For better performance with large files, TextReader can read blocks of characters. This example reads chunks of 10 characters at a time.

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        File.WriteAllText("lorem.txt", "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");

        using (TextReader reader = new StreamReader("lorem.txt"))
        {
            char[] buffer = new char[10];
            int charsRead;
            
            while ((charsRead = reader.ReadBlock(buffer, 0, buffer.Length)) > 0)
            {
                string chunk = new string(buffer, 0, charsRead);
                Console.WriteLine($"Read {charsRead} chars: {chunk}");
            }
        }
    }
}

ReadBlock reads up to the specified number of characters and returns the actual count read. The buffer is reused for each read operation. This approach is memory-efficient for large files.

The example shows how to process the exact number of characters read each time. The buffer size can be adjusted based on performance requirements and typical file sizes.

Using StringReader

StringReader is another TextReader implementation that reads from strings. This example demonstrates reading from an in-memory string.

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        string text = "Line 1\nLine 2\nLine 3";
        
        using (TextReader reader = new StringReader(text))
        {
            string line;
            int lineNumber = 1;
            
            while ((line = reader.ReadLine()) != null)
            {
                Console.WriteLine($"Line {lineNumber}: {line}");
                lineNumber++;
            }
        }
    }
}

StringReader provides the same interface as StreamReader but works with strings instead of files. This is useful for testing or when working with in-memory text data.

The example shows how StringReader can be used interchangeably with StreamReader when the text source changes. The same reading patterns apply to both implementations.

Peeking at the Next Character

TextReader's Peek method allows looking at the next character without consuming it. This example demonstrates peeking.

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        File.WriteAllText("peek.txt", "ABC");

        using (TextReader reader = new StreamReader("peek.txt"))
        {
            // Peek at first character
            int firstChar = reader.Peek();
            Console.WriteLine($"First character will be: {(char)firstChar}");
            
            // Now read it
            int readChar = reader.Read();
            Console.WriteLine($"Actually read: {(char)readChar}");
            
            // Verify they match
            Console.WriteLine($"Peeked and read match: {firstChar == readChar}");
        }
    }
}

Peek returns the next character without advancing the position. It returns -1 at end of stream, just like Read. This is useful for lookahead parsing.

The example shows how peeking allows inspection of the next character before deciding how to process it. The peeked character remains available for subsequent read operations.

Combining TextReader with Other APIs

TextReader integrates well with other .NET APIs. This example uses TextReader with LINQ to process lines.

Program.cs
using System;
using System.IO;
using System.Linq;

class Program
{
    static void Main()
    {
        File.WriteAllText("data.txt", "10\n20\n30\n40\n50");

        using (TextReader reader = new StreamReader("data.txt"))
        {
            var numbers = reader.ReadToEnd()
                .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(int.Parse)
                .Where(n =>  n > 25)
                .OrderByDescending(n =>  n);
            
            Console.WriteLine("Numbers > 25:");
            foreach (var num in numbers)
            {
                Console.WriteLine(num);
            }
        }
    }
}

The example reads all content, splits into lines, converts to integers, filters, and sorts. This shows TextReader's compatibility with LINQ operations for powerful text processing.

For very large files, consider using ReadLine in a loop with LINQ's lazy evaluation instead of ReadToEnd. The approach depends on file size and processing needs.

Source

TextReader Class Documentation

This tutorial covered reading text data in C# with TextReader, including line-by-line reading, complete content reading, and character processing. We explored both StreamReader and StringReader implementations.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.