File Io

By the end of this lesson you'll be able to read and write text files in C#, stream large files line by line, manage folders and build cross-platform paths, and parse multi-line data into records — the skills every real app needs to save and load data.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free C# course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

File.ReadAllText is like photocopying an entire document at once — fast and simple, but it all has to fit on the desk (in memory). A StreamReader is like reading a book one page at a time — slower to get going, but you can work through a 1,000-page novel without it ever overwhelming you. And a using block is the librarian who always returns the book to the shelf when you're done, so the next reader isn't locked out. Pick the photocopier for small files, the page-turner for huge ones.

A note on running these in the browser: the in-browser runner doesn't have a real, writable disk, so the File.* examples below are written for you to read and learn from (each has its expected output in comments). The interactive 🎯 YOUR TURN exercises use in-memory StringWriter / StringReader and string Split — the exact same logic, minus the disk — so they actually run . To run the real file code, install the .NET SDK locally.

All of these live in the System.IO namespace — forgetting using System.IO; at the top is the most common "the name File doesn't exist" error.

1. Reading & Writing Whole Files

The File class gives you one-line methods for the everyday jobs. WriteAllText creates the file (or overwrites it if it exists); AppendAllText adds to the end without erasing anything; ReadAllText loads the whole file into one string; and ReadAllLines gives you a string[] with one element per line. These are perfect for small files — read this worked example, then you'll do the same round-trip in memory.

Your turn. A StringWriter / StringReader behaves just like a file but lives in memory, so this one runs right here. Fill in the two ___ blanks, then run it.

2. StreamReader, StreamWriter & using

When a file is large, you don't want the whole thing in memory at once. Streams let you process it a line (or chunk) at a time. The catch: a stream holds an open handle on the file, and you must close it — otherwise the file stays locked and your last writes may never be saved. The using statement does this for you: it closes and flushes the stream automatically at the end of the block, even if an exception is thrown. Treat using as mandatory for every stream.

3. Directories & Paths

Never glue paths together with + and a slash — Windows uses \\ and Linux/Mac use / , and hard-coding one breaks the other. Path.Combine inserts the correct separator for you. Directory.Exists / CreateDirectory manage folders, and File.Exists lets you check before you read so you don't crash on a missing file. For literal Windows paths, a verbatim string @"..." means you don't have to escape every backslash.

Reading a file usually ends with parsing its text into useful pieces. Here you'll take a multi-line, comma-separated string (exactly what ReadAllText would hand you) and split it into records. Fill in the two ___ blanks, then run it.

The File helpers ( ReadAllText , WriteAllText ) are convenience wrappers: under the hood they open a stream, do the work, and close it for you. That's why they're great for small files — and why you reach for a raw StreamReader / StreamWriter when a file is too big to hold in memory or you want to write thousands of lines without rebuilding one giant string.

The third option — StringWriter / StringReader — uses the same stream API but the "file" is just an in-memory string. It's perfect for tests, for building text to return from a method, and (handily) for learning file logic in an environment with no disk.

For web apps, prefer the async versions — ReadAllTextAsync , WriteAllTextAsync — so a slow disk doesn't block other requests.

This small program uses everything from the lesson at once — it makes a folder, builds a cross-platform path, writes a header, appends entries through a using stream, then reads it all back and counts the entries. You understand every line now. (It writes to disk, so run it locally.)

Note new StreamWriter(logPath, append: true) — passing append: true adds to the file instead of overwriting it. Leave it off (the default) and you start fresh each time.

Q: When should I use File.ReadAllText versus a StreamReader ?

Use ReadAllText / ReadAllLines for small files — it's the simplest code and the size doesn't matter. Switch to a StreamReader when the file is large or you don't know its size, so you process it a line at a time without loading it all into memory.

Q: What does the using statement actually do?

It guarantees the stream is closed and flushed when the block ends — even if an exception is thrown. Without it, the file can stay locked and your final writes may never reach disk. Always wrap streams in using .

Q: Why does my program say it can't find a file that's clearly there?

A relative path like "data.txt" is resolved from the program's working directory (often the build output folder), not your source folder. Print Directory.GetCurrentDirectory() to see where it's looking, or use an absolute path / Path.Combine from a known root.

Q: How do I add to a file without wiping what's already in it?

Use File.AppendAllText , or open a StreamWriter with append: true . Plain File.WriteAllText and new StreamWriter(path) overwrite the whole file.

No blanks this time — just a brief and an outline. Take the in-memory "product,price" CSV string, split it into rows and then fields, parse each price, and add them up. This is the exact pattern you'd run on the text from File.ReadAllText — and it runs right here, no disk needed. Check your output against the expected line.

Practice quiz

What does File.WriteAllText do if the file already exists?

  • Appends to it
  • Throws an exception
  • Overwrites it
  • Does nothing

Answer: Overwrites it. WriteAllText creates the file or overwrites it entirely if it already exists. Use AppendAllText to add without erasing.

What does File.ReadAllLines return?

ReadAllLines returns a string[] — one element per line — whereas ReadAllText returns the whole file as one string.

Which method adds to the end of a file without erasing existing content?

  • File.WriteAllText
  • File.ReadAllText
  • File.Create
  • File.AppendAllText

Answer: File.AppendAllText. AppendAllText adds to the end without overwriting; WriteAllText would replace the whole file.

When should you prefer a StreamReader over File.ReadAllText?

  • For tiny files
  • For large files, so you process a line at a time without loading it all into memory
  • Never — File.ReadAllText is always better
  • Only on Windows

Answer: For large files, so you process a line at a time without loading it all into memory. Streams read a file line by line, ideal for large or unknown-size files because they never load the whole thing into memory.

What does wrapping a stream in a block guarantee?

  • The stream is closed and flushed at the end of the block, even if an exception is thrown
  • The file is encrypted
  • The data is compressed
  • The file is read twice

Answer: The stream is closed and flushed at the end of the block, even if an exception is thrown. using closes and flushes the stream automatically when the block ends, even on error — without it the file can stay locked and final writes may be lost.

What is the symptom of writing with a StreamWriter but never closing it?

  • The file is deleted
  • An immediate crash
  • Data is missing or truncated because the buffer was never flushed
  • The file becomes read-only

Answer: Data is missing or truncated because the buffer was never flushed. If the StreamWriter is never closed/flushed, buffered data never reaches disk — wrap it in using (or call Flush/Dispose).

Why use Path.Combine instead of gluing paths with + and a slash?

  • It's shorter to type
  • It inserts the correct separator for the OS, so code works cross-platform
  • It checks the file exists
  • It compresses the path

Answer: It inserts the correct separator for the OS, so code works cross-platform. Path.Combine inserts the right separator (\ on Windows, / on Linux/Mac), so hard-coding one doesn't break the other OS.

Which method lets you check a file is present before reading, without throwing?

  • File.ReadAllText
  • File.Open
  • Path.GetFileName
  • File.Exists

Answer: File.Exists. File.Exists returns true/false (never throws), so you can guard a read and avoid FileNotFoundException.

What do StringWriter and StringReader give you?

  • Faster disk access
  • The same stream API but backed by an in-memory string (no disk)
  • Automatic encryption
  • A way to delete files

Answer: The same stream API but backed by an in-memory string (no disk). StringWriter/StringReader use the same stream API but the 'file' is just an in-memory string — great for tests and disk-free environments.

A relative path like "data.txt" is resolved from where?

  • The source code folder
  • The system root
  • The program's working directory
  • The user's home folder

Answer: The program's working directory. Relative paths resolve from the program's working directory (often the build output), not the source folder — a common 'file not found' cause.