Files and Directories

Programs need to save data, read configuration, process logs and import spreadsheets. The System.IO namespace has everything for working with the file system: one-line helpers on File for whole files, streams for large ones, Path for building file names safely, and Directory for folders.

Reading and writing whole files

For small and medium files, the static File methods do everything in one call. They open, read or write, and close the file for you:

C#
File.WriteAllText("note.txt", "Buy milk\nCall the bank\n");
string content = File.ReadAllText("note.txt");
Console.Write(content);

File.AppendAllText("note.txt", "Book dentist\n");

string[] lines = File.ReadAllLines("note.txt");
Console.WriteLine($"{lines.Length} lines, last: {lines[^1]}");

File.WriteAllLines("numbers.txt", ["1", "2", "3"]);
int sum = File.ReadAllLines("numbers.txt").Sum(int.Parse);
Console.WriteLine($"Sum: {sum}");
Output
Buy milk
Call the bank
3 lines, last: Book dentist
Sum: 6

WriteAllText creates the file or replaces it entirely; AppendAllText adds to the end. Relative paths like "note.txt" are resolved from the program's current directory — usually the folder you ran it from.

Checking, copying, moving and deleting

C#
File.WriteAllText("draft.txt", "version 1");

Console.WriteLine(File.Exists("draft.txt"));
File.Copy("draft.txt", "backup.txt", overwrite: true);
File.Move("draft.txt", "final.txt", overwrite: true);
Console.WriteLine(File.Exists("draft.txt"));
Console.WriteLine(File.ReadAllText("final.txt"));

File.Delete("backup.txt");
File.Delete("does-not-exist.txt");   // no error if missing
Console.WriteLine(File.Exists("backup.txt"));

var info = new FileInfo("final.txt");
Console.WriteLine($"{info.Name}: {info.Length} bytes, extension {info.Extension}");
Output
True
False
version 1
False
final.txt: 9 bytes, extension .txt

Checking File.Exists before opening isn't a guarantee — another program could delete the file in between. Code that reads files should still be ready to catch IOException (and its subclasses like FileNotFoundException) and UnauthorizedAccessException.

Paths

Never build paths by gluing strings with "/" or "\\" — Windows and Unix use different separators. The Path class does it correctly on every platform:

C#
string path = Path.Combine("data", "2026", "sales.csv");
Console.WriteLine(path);
Console.WriteLine(Path.GetFileName(path));
Console.WriteLine(Path.GetFileNameWithoutExtension(path));
Console.WriteLine(Path.GetExtension(path));
Console.WriteLine(Path.GetDirectoryName(path));
Console.WriteLine(Path.ChangeExtension(path, ".json"));
Console.WriteLine(Path.IsPathRooted(path));
Output
data/2026/sales.csv
sales.csv
sales
.csv
data/2026
data/2026/sales.json
False

On Windows, the separators would print as backslashes. Other useful members: Path.GetTempPath(), Path.GetFullPath(…), and Path.GetRandomFileName().

Directories

C#
Directory.CreateDirectory(Path.Combine("photos", "2026", "summer"));   // creates all missing levels
File.WriteAllText(Path.Combine("photos", "2026", "summer", "beach.jpg"), "");
File.WriteAllText(Path.Combine("photos", "2026", "summer", "sunset.jpg"), "");
File.WriteAllText(Path.Combine("photos", "2026", "notes.txt"), "");

Console.WriteLine(Directory.Exists("photos"));

foreach (string file in Directory.GetFiles("photos", "*.jpg", SearchOption.AllDirectories).Order())
    Console.WriteLine(file);

foreach (string dir in Directory.GetDirectories("photos"))
    Console.WriteLine($"subfolder: {Path.GetFileName(dir)}");

Directory.Delete("photos", recursive: true);
Console.WriteLine(Directory.Exists("photos"));
Output
True
photos/2026/summer/beach.jpg
photos/2026/summer/sunset.jpg
subfolder: 2026
False

The order in which the file system returns entries isn't defined, so sort the results when order matters. For very large folders, Directory.EnumerateFiles returns results lazily instead of building the whole array first.

Streaming large files

ReadAllLines loads the whole file into memory. For big files — a multi-gigabyte log — read one line at a time with File.ReadLines, which is lazy, or a StreamReader:

C#
using (var writer = new StreamWriter("server.log"))
{
    for (int i = 1; i <= 1000; i++)
        writer.WriteLine(i % 250 == 0 ? $"{i} ERROR disk slow" : $"{i} INFO ok");
}

int errors = File.ReadLines("server.log").Count(line => line.Contains("ERROR"));
Console.WriteLine($"{errors} errors");

using var reader = new StreamReader("server.log");
string? line;
int lineNo = 0;
while ((line = reader.ReadLine()) is not null)
{
    lineNo++;
    if (line.Contains("ERROR"))
    {
        Console.WriteLine($"First error on line {lineNo}: {line}");
        break;
    }
}
Output
4 errors
First error on line 250: 250 ERROR disk slow

Because File.ReadLines is lazy, LINQ methods like First and Take stop reading as soon as they have what they need.

A small CSV example

Comma-separated files are everywhere. For simple data without quoted commas, Split is enough; for real-world CSV with quotes and escapes, use a library such as CsvHelper (lesson 60 shows how to add packages):

TXT sales.csv
region,product,units,price
North,Widget,12,2.50
South,Gadget,4,10.00
North,Gadget,7,10.00
East,Widget,20,2.50
C#
var rows = File.ReadLines("sales.csv")
    .Skip(1)                                   // header
    .Select(line => line.Split(','))
    .Select(f => (Region: f[0], Units: int.Parse(f[2]), Price: decimal.Parse(f[3])));

foreach (var g in rows.GroupBy(r => r.Region).OrderBy(g => g.Key))
    Console.WriteLine($"{g.Key,-6}{g.Sum(r => r.Units * r.Price),8:C}");
Output
East    $50.00
North  $100.00
South   $40.00

Binary files

File.ReadAllBytes and File.WriteAllBytes handle raw bytes — images, archives, anything that isn't text:

C#
byte[] data = [0x89, 0x50, 0x4E, 0x47];
File.WriteAllBytes("header.bin", data);
byte[] back = File.ReadAllBytes("header.bin");
Console.WriteLine(Convert.ToHexString(back));
Console.WriteLine(System.Text.Encoding.ASCII.GetString(back, 1, 3));
Output
89504E47
PNG

Async file methods

Every File helper has an async version (ReadAllTextAsync, WriteAllLinesAsync, …). In web servers and UI apps, prefer them so threads aren't blocked waiting on the disk:

C#
await File.WriteAllTextAsync("async.txt", "written asynchronously");
string text = await File.ReadAllTextAsync("async.txt");
Console.WriteLine(text);
Output
written asynchronously

Try it yourself

Write a few journal entries to a file, one per line with a date, then read them back and print only the entries from March.

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET
Output
Mar 2: Finished the collections lessons
Mar 15: Built my first class hierarchy