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:
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}");
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
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}");
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:
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));
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
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"));
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:
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; } }
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):
region,product,units,price North,Widget,12,2.50 South,Gadget,4,10.00 North,Gadget,7,10.00 East,Widget,20,2.50
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}");
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:
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));
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:
await File.WriteAllTextAsync("async.txt", "written asynchronously"); string text = await File.ReadAllTextAsync("async.txt"); Console.WriteLine(text);
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.
Mar 2: Finished the collections lessons Mar 15: Built my first class hierarchy