Site

File I/O — C# System.IO

Tutorial S5  •  C# / Learn

S5.0 What This Teaches

This tutorial covers file and path operations in C#:

S5.1 File Convenience Methods

using System.IO;

// Write all text at once (creates or overwrites)
File.WriteAllText("notes.txt", "Hello, file!");

// Append text
File.AppendAllText("notes.txt", "\nSecond line.");

// Read all text
string content = File.ReadAllText("notes.txt");
Console.WriteLine(content);

// Read all lines into an array
string[] lines = File.ReadAllLines("notes.txt");
foreach (string line in lines)
    Console.WriteLine(line);

// Write multiple lines
File.WriteAllLines("list.txt", new[] { "alpha", "beta", "gamma" });
File convenience methods are best for small files that fit in memory. They open, read/write, and close in one call. Use streaming APIs for large files.

S5.2 StreamReader and StreamWriter

// Write line by line
using var writer = new StreamWriter("output.txt");
for (int i = 1; i <= 5; i++)
    writer.WriteLine($"Line {i}");
// writer.Dispose() called automatically at end of scope

// Read line by line - efficient for large files
using var reader = new StreamReader("output.txt");
string? line;
while ((line = reader.ReadLine()) != null)
    Console.WriteLine(line);

// Append to existing file
using var appender = new StreamWriter("output.txt", append: true);
appender.WriteLine("Appended line");

S5.3 Path and Directory Operations

string full = Path.GetFullPath("notes.txt");
string dir  = Path.GetDirectoryName(full)!;
string name = Path.GetFileName(full);        // "notes.txt"
string stem = Path.GetFileNameWithoutExtension(full);  // "notes"
string ext  = Path.GetExtension(full);       // ".txt"

// Build paths in a platform-safe way
string path = Path.Combine(dir, "subdir", "file.txt");

// Test existence
bool fileExists = File.Exists("notes.txt");
bool dirExists  = Directory.Exists("mydir");

// Create and delete
Directory.CreateDirectory("newdir");
Directory.Delete("newdir", recursive: false);
File.Delete("notes.txt");

// Enumerate files
foreach (string f in Directory.EnumerateFiles(".", "*.txt"))
    Console.WriteLine(Path.GetFileName(f));

S5.4 Binary Files

// Write bytes
byte[] data = { 1, 2, 3, 4, 5 };
File.WriteAllBytes("data.bin", data);

// Read bytes
byte[] read = File.ReadAllBytes("data.bin");
Console.WriteLine(read.Length);   // 5

// BinaryWriter / BinaryReader for typed data
using var bw = new BinaryWriter(File.OpenWrite("record.bin"));
bw.Write(42);        // int (4 bytes)
bw.Write(3.14);      // double (8 bytes)
bw.Write("hello");   // length-prefixed string

using var br = new BinaryReader(File.OpenRead("record.bin"));
Console.WriteLine(br.ReadInt32());   // 42
Console.WriteLine(br.ReadDouble());  // 3.14
Console.WriteLine(br.ReadString());  // hello

S5.5 Example - All Together

// File I/O - write CSV and read it back, counting lines.

using System.IO;

string csvPath = "scores.csv";

// Write CSV
var records = new[] { ("Alice", 92), ("Bob", 78), ("Carol", 95) };
using (var w = new StreamWriter(csvPath))
{
    w.WriteLine("Name,Score");
    foreach (var (name, score) in records)
        w.WriteLine($"{name},{score}");
}

// Read and process
int count = 0;
int total = 0;
using (var r = new StreamReader(csvPath))
{
    r.ReadLine();   // skip header
    string? line;
    while ((line = r.ReadLine()) != null)
    {
        var parts = line.Split(',');
        total += int.Parse(parts[1]);
        count++;
    }
}
Console.WriteLine($"Average score: {total / (double)count:F1}");
Expected output:
Average score: 88.3

S5.6 Exercise

Exercise
  • Write a program that creates a file, writes 10 lines to it with StreamWriter, then reads and prints each line with StreamReader.
  • Use Directory.EnumerateFiles to list all .cs files in the current directory (or a test directory). Print the total line count across all files.
  • Write and read a binary file using BinaryWriter and BinaryReader to store an array of double values. Verify the round-trip is lossless.

S5.7 Common Mistakes

Forgetting to close or dispose file handles

Not wrapping streams in using leaves file handles open until the GC finalizes the object, which is non-deterministic. On Windows this prevents other processes from opening the file. Always use using.

Using + for path concatenation

string path = dir + "\\" + name;   // breaks on Linux (different separator)
string path = Path.Combine(dir, name);  // correct - portable

Reading a huge file with ReadAllText

File.ReadAllText loads the entire file into a string. For files larger than a few MB, use StreamReader to process line by line, or File.ReadLines which returns an IEnumerable<string> and reads lazily.

S5.8 Key Terms

TermMeaning
FileStatic class with convenience methods for common file operations
StreamReaderReads characters from a byte stream; buffers for efficiency
StreamWriterWrites characters to a byte stream; buffers writes
PathStatic class for manipulating path strings portably
DirectoryStatic class for directory creation, deletion, and enumeration
BinaryReader/WriterReads/writes primitive types as raw bytes
usingEnsures Dispose() on streams; guarantees handle is released
File.ReadLinesLazy enumeration of lines; preferred over ReadAllLines for large files