Reading configuration files, writing logs, and parsing CSV are done by almost every app. C# provides a rich API for handling files with the System.IO namespace. Short operations are handled by the File static method, while larger files are handled by the Stream family.
Learning Objectives
- Know how to use
File.ReadAllText/WriteAllText/ReadAllLines/WriteAllLines - Learn the pattern of reading line by line with
StreamReader - Manipulate OS-independent paths with the
Pathclass - Create a safe temporary file with
Path.GetTempFileName() - Experience the basics of
asyncasynchronous file I/O
Key concepts
1) File static method (for small files)
This is the simplest way to read and write the entire file at once.
File.WriteAllText("a.txt", "hello"); //Write entire string
string text = File.ReadAllText("a.txt"); //read full
File.WriteAllLines("b.txt", new[] {"a","b"}); //write line by line
string[] lines = File.ReadAllLines("b.txt"); //Read line by line (array)
If it's more than a few tens of MB, the stream method is better because it eats up all the memory.
2) StreamReader for large files
Reading and processing line by line uses less memory. Since it is IDisposable, wrap it as using.
using var sr = new StreamReader(path);
string? line;
while ((line = sr.ReadLine()) is not null)
{
//line processing
}
3) Path — path manipulation
Instead of connecting directly to "/" or "\\", use Path.Combine. This is because the separator is different for each OS.
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(); //Create empty file in OS temporary folder
Path.GetTempFileName() automatically creates conflict-free files. It is safe to use this as a learning example.
4) Handling I/O exceptions
File operations almost always have the potential to fail (missing file, insufficient permissions, etc.).
try
{
string s = File.ReadAllText(path);
}
catch (FileNotFoundException) { /* ... */ }
catch (UnauthorizedAccessException) { /* ... */ }
catch (IOException) { /*Other I/O errors*/ }
5) Asynchronous I/O (async/await)
Disks are slow. In the UI or server, it is processed asynchronously and does not block other operations. This is covered in detail in Part 19, but the usage is simple — the method name is followed by Async and is received as await.
string s = await File.ReadAllTextAsync(path);
await File.WriteAllTextAsync(path, s);
Full-scale
async/awaitlearning is covered in Part 19. Just learn the shape here.
See as example
Example 1 — WriteRead: Write and read entire text at once
string path = Path.GetTempFileName(); //temporary files
File.WriteAllText(path, "Hello, file!\nSecond line");
string text = File.ReadAllText(path);
Console.WriteLine("== What you read ==");
Console.WriteLine(text);
File.Delete(path); //organizing
Execution result
== What you read ==
Hello, File!
second line
Note: Path.GetTempFileName() creates conflict-free files in the OS temporary folder.
Example 2 — WriteReadLines: Dealing with arrays of lines
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);
Execution result
- Alice
- Bob
- Charlie
Memo: The cleanest line-by-line data is WriteAllLines/ReadAllLines.
Example 3 — StreamReaderUse: Stream line by line
string path = Path.GetTempFileName();
File.WriteAllLines(path, ["apple", "banana", "cherry"]);
//Explicitly limit the lifetime of sr with a using block.
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);
Execution result
1: apple
2: Banana
3: Cherry
Note: Memory usage is consistent even with large files. When you exit the using block, the handle is closed immediately, and the immediately following File.Delete does not get caught in Windows' file lock (if it were using var, the handle would be maintained until the end of the method scope, causing a crash).
Example 4 — PathHelpers: Splitting and merging paths
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);
Execution result
Combine : logs/2025/app.log
FileName : app.log
Extension : .log
NoExt : app
Temp : /tmp/tmpXXXXXX.tmp
Memo: In Windows, \ is entered. Do not connect manually, use Path.Combine.
Example 5 — AsyncFile: Asynchronous read and write
//top-level await: Available starting from C# 9 / .NET 5+
string path = Path.GetTempFileName();
await File.WriteAllTextAsync(path, "Save asynchronously!");
string text = await File.ReadAllTextAsync(path);
Console.WriteLine($"Read: {text}");
File.Delete(path);
Execution result
Read: Save asynchronously!
Note: The behavior is the same as the synchronous version, but you can do other things while the disk is waiting. This is covered in detail in Part 19.
Common Mistakes
- Directly connect the path to
"a" + "/" + "b"— OS dependent. UsePath.Combine. - Reading large files with
ReadAllText— memory explosion. Line by line as a stream. - Using
StreamReaderwithoutusing— file handle leak. - Placing temporary files in your working directory — polluting your environment. Use
Path.GetTempFileName(). - Missing exception handling — Prepare for missing files, permissions, disk full, etc.
Cleanup
- Small text:
File.ReadAllText/WriteAllText - Joule unit:
File.ReadAllLines/WriteAllLines - Large files:
StreamReader+using - The path is
Path.Combineand the temporary file isPath.GetTempFileName(). - In places where the disk is slow,
*Asyncversion +await(Preview of Part 19)
Try it yourself
cd src/WriteRead && dotnet run
cd ../WriteReadLines && dotnet run
cd ../StreamReaderUse && dotnet run
cd ../PathHelpers && dotnet run
cd ../AsyncFile && dotnet run