CodeGym /Courses /C# SELF /Reading text files: Stream...

Reading text files: StreamReader

C# SELF
Level 36 , Lesson 2
Available

1. Introduction

Let's imagine we need to read the contents of a text file again. Suppose we have a file with cool programmer quotes that we want to display in our already familiar text app. Yeah, we've already seen high-level methods like File.ReadAllText, but what if the file is huge and we need to read it line by line instead of loading the whole thing? Or maybe we want to read the file in a controlled way, like slowly loading lines so we don't blow up our memory?

That's where StreamReader comes in — it's a class that's perfect for reading text files line by line, in chunks, or even character by character — basically, it's flexible and convenient.

How does StreamReader work?

StreamReader is a class from the System.IO namespace that reads text data from a stream (usually a file, but it could be a network stream or memory too). It can read data in the encoding you need, parses it into characters and strings, and gives you nice data types: strings and chars.

If you draw this as a diagram, it looks something like this:


[ File (bytes) ] --(FileStream)--> [ StreamReader (parses char codes) ] ---> [ Your code (string, char) ]
  • File (or other byte source): Gives us bytes.
  • FileStream: Reads those bytes as a "channel".
  • StreamReader: Turns bytes into characters and strings with the right encoding (default is UTF-8).

Why shouldn't you always just use File.ReadAllText?

In real life, files can be big. Like, really big (think logs or csvs with a million lines). Trying to read those files all at once into memory is like trying to eat a whole cake in one go: tasty, but dangerous for your health.
StreamReader reads the file "in chunks". It doesn't load the whole file into memory, but only loads and gives you the next line or char when you actually need it. It's efficient and super handy.

2. Reading a file line by line

Let's create a file called quotes.txt with these lines:


Code is poetry.
Debugging is detective work.
"Doesn't work" is the best bug report.

Now let's add some code to our learning app (let's say it's a simple console app we're building up step by step). Put the file next to the .exe — let the program read it and print the contents line by line.

Code with comments:


// Get the path to the file in the program's folder
string fileName = "quotes.txt";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

// Check if the file exists
if (!File.Exists(filePath))
{
    Console.WriteLine($"File not found: {filePath}");
    return;
}

// Open the file for reading using StreamReader
using StreamReader reader = new StreamReader(filePath);
string? line;
int lineNumber = 1;

// Read the file line by line until the end
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine($"{lineNumber,2}: {line}");
    lineNumber++;
}

What's going on here?

  • We figure out the path to our file using Path.Combine and AppDomain.CurrentDomain.BaseDirectory — this way the code works the same on Windows, Linux, and even in Docker containers.
  • First, we check that the file actually exists.
  • With using we create a StreamReader and read the file one line at a time (with ReadLine()).
  • At the end of the using block, the file is closed, even if you forgot to do it manually or got an exception.

Visualization: how it works


[ quotes.txt ] --(FileStream)--> [ StreamReader ] --(ReadLine)--> [ string line ] --(Console.WriteLine)--> Screen
    ^                                                       
    |
AppDomain.CurrentDomain.BaseDirectory + Path.Combine

3. Features and gotchas

How line reading works

The ReadLine() method returns a string up to the first newline character (\n or \r\n). When the file ends, it returns null. That's why the loop usually looks like this:


string? line;
while ((line = reader.ReadLine()) != null)
{
    // process the line
}

Common mistakes with StreamReader

Sometimes it's tempting not to use using and just write:


StreamReader reader = new StreamReader(filePath);
// ...
reader.Close();

This is like forgetting to close the door behind you in winter: anything can happen. If an error pops up while reading (like the disk suddenly becomes unavailable), the code to close the file won't run, and the file will stay open in the system.
So using using is not just a recommendation, it's a real necessity. Your future sysadmin coworker will thank you for not leaving a zoo of "hanging" files behind.

Short version with using declaration

With modern C# (starting from 8.0) you can write it shorter:


using StreamReader reader = new StreamReader(filePath);
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line);
}

Dispose will be called at the end of the current block (method).

4. Handy tips

Reading a file with unknown encoding

By default, StreamReader works with UTF-8. But sometimes you get files with a different encoding (like Windows-1251 for old Russian texts). In that case, your program might print "?" or unreadable characters.

You can specify the encoding explicitly:


using System.Text;

// Open the file as Windows-1251
using StreamReader reader = new StreamReader(filePath, Encoding.GetEncoding("windows-1251"));

Docs on encodings and Encoding

Reading the whole file as one big text

Sometimes you need to get the whole file as one text, but not with File.ReadAllText, but with StreamReader.


using StreamReader reader = new StreamReader(filePath);
string allText = reader.ReadToEnd();
Console.WriteLine(allText);
  • The ReadToEnd() method reads all the file contents from the current position to the end as one string.
  • For big files, this can be inefficient and you risk getting OutOfMemoryException. For files up to a few megabytes — it's totally fine.

How this is used in real life

  • Log processing. If you have a server log file, it's convenient to read it line by line, filter the events you need, and not load everything into memory at once.
  • CSV import. If you need to parse big tables, it's handy to pull data line by line and process it as it comes in.
  • Searching for keywords in big text files. You can look for the text you need among millions of lines without worrying about memory limits.
  • Unit tests. Test data files are often read line by line with StreamReader.

Comparison of reading methods

Method When to use Pros Cons
File.ReadAllText
Small files One line of code, fast Big file – lots of memory
StreamReader.ReadLine()
Any, especially big files Line-by-line reading, low memory A bit more code, logic is a bit trickier
StreamReader.ReadToEnd()
Small/medium files Flexible encoding control Hard with really big files

5. Practice

Let's make the task a bit harder. Let our program print only the first N lines of the file, where the number is entered by the user. Admit it, sometimes you don't want to see hundreds of lines at once — a couple of quotes is enough for motivation. :)

Here's how to do it:


using System;
using System.IO;

class Program
{
    static void Main()
    {
        string fileName = "quotes.txt";
        string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

        if (!File.Exists(filePath))
        {
            Console.WriteLine($"File not found: {filePath}");
            return;
        }

        Console.Write("How many lines to print? ");
        string? input = Console.ReadLine();
        if (!int.TryParse(input, out int linesToShow) || linesToShow < 1)
        {
            Console.WriteLine("Error: Enter a valid number greater than 0.");
            return;
        }

        using StreamReader reader = new StreamReader(filePath);

        int current = 0;
        string? line;
        while (current < linesToShow && (line = reader.ReadLine()) != null)
        {
            Console.WriteLine(line);
            current++;
        }

        if (current == 0)
            Console.WriteLine("File is empty or couldn't read the first line.");
        else if (current < linesToShow)
            Console.WriteLine($"There were only {current} line(s) in the file.");
    }
}

6. Important points and common pitfalls

When you're working with StreamReader, the main thing is to remember proper resource management (see the previous lecture about IDisposable). If you forget, you might get an error like "file is already used by another process" or "too many open files".

Another thing — be careful with encoding. If you're not sure the file is in UTF-8, specify the encoding you need. There's a second parameter in the StreamReader constructor for that.

One more practical "gotcha" is if the file is being updated while your program is running (like a log file). You might not always see new lines right away if the file is used by other processes — then you need to re-read the file or use special methods, but we'll talk about that later.

2
Task
C# SELF, level 36, lesson 2
Locked
Reading the first N lines of a file
Reading the first N lines of a file
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION