← Back to C# series
πŸ“‚
Exceptions & I/O
Exceptions/IO Β· Prerequisite: exception handling

16. File I/O

Read and write text files with File, StreamReader, and StreamWriter; manipulate the filesystem with Path and Directory. Pair with using to release resources safely.

C#.NET 8fileIO
Duration
⏱ ~1-1.5 hours
Level
πŸ“Š Intermediate
Prerequisite
🎯 Exception handling
OUTCOME
Read and write text files with File, StreamReader, and StreamWriter; manipulate the filesystem with Path and Directory. Pair with using to release resources safely.

What you'll learn

  • 1Use `File.ReadAllText`/`WriteAllText`/`ReadAllLines`/`WriteAllLines`
  • 2Read line-by-line with `StreamReader`
  • 3Manipulate OS-independent paths with the `Path` class
  • 4Create safe temp files with `Path.GetTempFileName()`
  • 5Get a taste of `async` file I/O

Overview

Almost every app reads config files, writes logs, or parses CSV. C# offers a rich `System.IO` namespace. Use `File` static methods for short tasks, and the `Stream` family for large files.

Core Concepts

1) `File` static methods (small files)

Simplest way to read/write a file in one go.

csharp
File.WriteAllText("a.txt", "Hi");              // write whole string
string text = File.ReadAllText("a.txt");        // read entire content

File.WriteAllLines("b.txt", new[] {"a","b"});   // write line-by-line
string[] lines = File.ReadAllLines("b.txt");    // read line-by-line (array)

Above tens of MB this eats memory; use streams instead.

2) `StreamReader` (large files)

Reading one line at a time keeps memory low. It implements `IDisposable`, so wrap with `using`.

csharp
using var sr = new StreamReader(path);
string? line;
while ((line = sr.ReadLine()) is not null)
{
    // process line
}

3) `Path` β€” path manipulation

Don't manually concatenate with `"/"` or `"\\"` β€” use `Path.Combine`. The separator differs by OS.

csharp
string p = Path.Combine("logs", "2025", "app.log");
string name = Path.GetFileName(p);       // "app.log"
string ext  = Path.GetExtension(p);      // ".log"
string tmp  = Path.GetTempFileName();    // empty file in OS temp dir

`Path.GetTempFileName()` makes a non-colliding file automatically. Safe for learning examples.

4) Pair with exception handling

File ops can almost always fail (missing file, no permission, etc.).

csharp
try
{
    string s = File.ReadAllText(path);
}
catch (FileNotFoundException)   { /* ... */ }
catch (UnauthorizedAccessException) { /* ... */ }
catch (IOException) { /* other I/O errors */ }

5) Async I/O (`async`/`await`)

Disks are slow. In UI and server code, do I/O asynchronously so other work isn't blocked. Lecture 19 covers this in depth, but the form is simple β€” method name ends in `Async` and you `await` the result.

csharp
string s = await File.ReadAllTextAsync(path);
await File.WriteAllTextAsync(path, s);

> Detailed `async`/`await` study is in **lecture 19**. Just learn the shape here.

Examples

Example 1 β€” `WriteRead`: write and read full text

csharp
string path = Path.GetTempFileName();           // temp file

File.WriteAllText(path, "Hello, file!\nSecond line");

string text = File.ReadAllText(path);
Console.WriteLine("== Read content ==");
Console.WriteLine(text);

File.Delete(path);                              // cleanup

**Output**

text
== Read content ==
Hello, file!
Second line

**Note:** `Path.GetTempFileName()` creates a non-colliding file in the OS temp folder.

Example 2 β€” `WriteReadLines`: handle as a line array

csharp
string path = Path.GetTempFileName();

string[] names = ["Alice", "Bob", "Charlie"];
File.WriteAllLines(path, names);

string[] loaded = File.ReadAllLines(path);
foreach (var n in loaded)
    Console.WriteLine($"- {n}");

File.Delete(path);

**Output**

text
- Alice
- Bob
- Charlie

**Note:** For line-oriented data, `WriteAllLines`/`ReadAllLines` are cleanest.

Example 3 β€” `StreamReaderUse`: stream line-by-line

csharp
string path = Path.GetTempFileName();
File.WriteAllLines(path, ["apple", "banana", "cherry"]);

// Use a using block to scope sr's lifetime explicitly.
using (var sr = new StreamReader(path))
{
    int lineNo = 1;
    string? line;
    while ((line = sr.ReadLine()) is not null)
    {
        Console.WriteLine($"{lineNo}: {line}");
        lineNo++;
    }
}

File.Delete(path);

**Output**

text
1: apple
2: banana
3: cherry

**Note:** Memory stays flat even for big files. Leaving the `using` block immediately closes the handle so the following `File.Delete` won't hit a Windows file lock (`using var` would hold the handle to end-of-method and conflict).

Example 4 β€” `PathHelpers`: split and join paths

csharp
string combined = Path.Combine("logs", "2025", "app.log");
Console.WriteLine($"Combine    : {combined}");
Console.WriteLine($"FileName   : {Path.GetFileName(combined)}");
Console.WriteLine($"Extension  : {Path.GetExtension(combined)}");
Console.WriteLine($"NoExt      : {Path.GetFileNameWithoutExtension(combined)}");

string temp = Path.GetTempFileName();
Console.WriteLine($"Temp       : {temp}");
File.Delete(temp);

**Output**

text
Combine    : logs/2025/app.log
FileName   : app.log
Extension  : .log
NoExt      : app
Temp       : /tmp/tmpXXXXXX.tmp

**Note:** On Windows you'd see `\`. **No manual concatenation β€” use `Path.Combine`**.

Example 5 β€” `AsyncFile`: async read/write

csharp
// top-level await: available from C# 9 / .NET 5+
string path = Path.GetTempFileName();

await File.WriteAllTextAsync(path, "Stored async!");
string text = await File.ReadAllTextAsync(path);

Console.WriteLine($"Read: {text}");

File.Delete(path);

**Output**

text
Read: Stored async!

**Note:** Same behavior as the sync version, but other work can run while the disk is busy. More in lecture 19.

Full example code (src/)

src/AsyncFile/AsyncFile.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx16</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/AsyncFile/Program.cs

csharp
// async file I/O (top-level await β€” C# 9 / .NET 5+)
// Detailed async/await is in lecture 19
string path = Path.GetTempFileName();

await File.WriteAllTextAsync(path, "Stored async!");
string text = await File.ReadAllTextAsync(path);

Console.WriteLine($"Read: {text}");

File.Delete(path);

src/PathHelpers/PathHelpers.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx16</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/PathHelpers/Program.cs

csharp
// Path utility methods
string combined = Path.Combine("logs", "2025", "app.log");
Console.WriteLine($"Combine    : {combined}");
Console.WriteLine($"FileName   : {Path.GetFileName(combined)}");
Console.WriteLine($"Extension  : {Path.GetExtension(combined)}");
Console.WriteLine($"NoExt      : {Path.GetFileNameWithoutExtension(combined)}");

string temp = Path.GetTempFileName();
Console.WriteLine($"Temp       : {temp}");
File.Delete(temp);

src/StreamReaderUse/Program.cs

csharp
// StreamReader: streams line-by-line (large-file friendly)
string path = Path.GetTempFileName();
File.WriteAllLines(path, ["apple", "banana", "cherry"]);

// Scope sr explicitly with a using block β†’ Dispose on block exit.
// With `using var`, the handle would live to end-of-method and on Windows the
// File.Delete below would hit a file lock.
using (var sr = new StreamReader(path))
{
    int lineNo = 1;
    string? line;
    while ((line = sr.ReadLine()) is not null)
    {
        Console.WriteLine($"{lineNo}: {line}");
        lineNo++;
    }
}

// sr is disposed by now, so delete is safe.
File.Delete(path);

src/StreamReaderUse/StreamReaderUse.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx16</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/WriteRead/Program.cs

csharp
// Write full text to a temp file and read it back
string path = Path.GetTempFileName();

File.WriteAllText(path, "Hello, file!\nSecond line");

string text = File.ReadAllText(path);
Console.WriteLine("== Read content ==");
Console.WriteLine(text);

File.Delete(path);                          // leave no traces in the working dir

src/WriteRead/WriteRead.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx16</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/WriteReadLines/Program.cs

csharp
// Write and read line-by-line in one shot
string path = Path.GetTempFileName();

string[] names = ["Alice", "Bob", "Charlie"];
File.WriteAllLines(path, names);

string[] loaded = File.ReadAllLines(path);
foreach (var n in loaded)
{
    Console.WriteLine($"- {n}");
}

File.Delete(path);

src/WriteReadLines/WriteReadLines.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx16</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Common Mistakes

  1. Hand-concatenating with `"a" + "/" + "b"` β€” OS-dependent. Use `Path.Combine`.
  2. Reading a huge file with `ReadAllText` β€” memory blow-up. Stream line by line.
  3. Using `StreamReader` without `using` β€” file-handle leak.
  4. Dropping temp files in the working dir β€” pollutes the environment. Use **`Path.GetTempFileName()`**.
  5. Skipping exception handling β€” prepare for missing files, permissions, full disks, etc.

Summary

  • Small text: `File.ReadAllText`/`WriteAllText`
  • Line-oriented: `File.ReadAllLines`/`WriteAllLines`
  • Big files: `StreamReader` + `using`
  • Paths: `Path.Combine`; temp files: `Path.GetTempFileName()`
  • On slow disks, use `*Async` + `await` (preview of lecture 19)

Practice

**Practice - 16. File I/O**

Problem 1 β€” Number-prefixed output

  • Project folder: `Homework01/`
  • Key concepts: `Path.GetTempFileName`, `File.WriteAllLines`, `File.ReadAllLines`

Requirements

  • Create a temp file with `Path.GetTempFileName()`.
  • Write `["Seoul", "Busan", "Daegu", "Gwangju"]` line-by-line.
  • Read it back and print as `[1] Seoul` etc.
  • Delete the file before exit.

Expected output

text
[1] Seoul
[2] Busan
[3] Daegu
[4] Gwangju

Hints

  • Use `File.WriteAllLines(path, lines)` and `File.ReadAllLines(path)`.
  • Index with `for (int i = 0; i < lines.Length; i++)` or an `Enumerate` pattern.

Problem 2 β€” CSV column sum

  • Project folder: `Homework02/`
  • Key concepts: line/field parsing, numeric conversion, accumulation

Requirements

  • Write a CSV like this (header + 3 rows) to a temp file:

``` name,score Alice,80 Bob,95 Charlie,72 ```

  • Read line-by-line, skipping the header, and sum the second column (`score`).
  • Print sum and average (integer division is fine).

Expected output

text
Sum: 247
Average: 82

Hints

  • Split columns with `string.Split(',')`.
  • Convert with `int.Parse(parts[1])`.
  • Skip the header with `continue;` when `i == 0`.

Check your answer

Try it yourself, then compare against the [`answer/`](./answer/) folder.

Answer (answer/)

homework/answer/Homework01/Homework01.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx16</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

homework/answer/Homework01/Program.cs

csharp
// Write to a temp file, then print with numbers
string path = Path.GetTempFileName();

string[] cities = ["Seoul", "Busan", "Daegu", "Gwangju"];
File.WriteAllLines(path, cities);

string[] loaded = File.ReadAllLines(path);
for (int i = 0; i < loaded.Length; i++)
{
    Console.WriteLine($"[{i + 1}] {loaded[i]}");
}

File.Delete(path);

homework/answer/Homework02/Homework02.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx16</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

homework/answer/Homework02/Program.cs

csharp
// Build a temp CSV file and compute sum/average of the score column
string path = Path.GetTempFileName();

string[] csv =
[
    "name,score",
    "Alice,80",
    "Bob,95",
    "Charlie,72",
];
File.WriteAllLines(path, csv);

string[] lines = File.ReadAllLines(path);
int total = 0;
int count = 0;

for (int i = 0; i < lines.Length; i++)
{
    if (i == 0) continue;                       // skip header
    string[] parts = lines[i].Split(',');
    total += int.Parse(parts[1]);
    count++;
}

Console.WriteLine($"Sum: {total}");
Console.WriteLine($"Average: {total / count}");

File.Delete(path);

Try It Yourself

bash
cd src/WriteRead && dotnet run
cd ../WriteReadLines && dotnet run
cd ../StreamReaderUse && dotnet run
cd ../PathHelpers && dotnet run
cd ../AsyncFile && dotnet run

Next Lecture

[17_String_Processing](../17_%EB%AC%B8%EC%9E%90%EC%97%B4_%EC%B2%98%EB%A6%AC/) β€” Tools to manipulate the text you read in.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—