File & Stream Operations
Working with the file system is a daily task for most applications. .NET provides two levels of abstraction: the static File and Directory helpers for simple, one-shot operations, and the streaming classes StreamReader / StreamWriter for fine-grained control over large files or custom encodings.
File — Static Helper Methods
System.IO.File exposes convenient static methods that open, operate, and close a file in one call:
| Method | Purpose |
|---|---|
File.ReadAllText(path) | Read entire file as a single string |
File.WriteAllText(path, text) | Create/overwrite file with a string |
File.AppendAllText(path, text) | Append text to existing file |
File.ReadAllLines(path) | Read all lines into a string[] |
File.WriteAllLines(path, lines) | Write a string collection, one line each |
File.Exists(path) | Check whether a file exists |
File.Delete(path) | Delete a file |
File.Copy(src, dest) | Copy a file |
These methods are ideal for small files where loading everything into memory is acceptable.
StreamReader / StreamWriter
For large files, or when you need to process line-by-line without loading everything at once, use StreamReader and StreamWriter. Always wrap them in a using statement to guarantee the underlying file handle is closed:
using var reader = new StreamReader(path);
string? line;
while ((line = reader.ReadLine()) != null)
Process(line);The Path Class
System.IO.Path provides cross-platform path manipulation without string concatenation:
Path.Combine("folder", "sub", "file.txt")— OS-correct separatorPath.GetExtension(path)—".txt"Path.GetFileName(path)—"file.txt"Path.GetFileNameWithoutExtension(path)—"file"Path.GetDirectoryName(path)— parent directoryPath.GetTempPath()— system temp folder
Directory Class and DirectoryInfo
Directory.GetFiles(path, pattern) returns all matching file paths. Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories) searches recursively. Directory.CreateDirectory(path) creates a directory tree if it does not exist.
FileInfo and DirectoryInfo are the object-oriented counterparts — useful when you need to perform multiple operations on the same path since they cache the path and expose properties like Length, LastWriteTime, and Exists.
Code Examples
using System;
using System.IO;
string tempDir = Path.GetTempPath();
string filePath = Path.Combine(tempDir, "demo.txt");
// Write — creates or overwrites the file
string[] lines =
{
"Line 1: C# File I/O",
"Line 2: Using System.IO",
"Line 3: Cross-platform paths",
};
File.WriteAllLines(filePath, lines);
Console.WriteLine(````````Written to: ${filePath}````````);
// Append a line
File.AppendAllText(filePath, "Line 4: Appended later\n");
// Read entire file as one string
string allText = File.ReadAllText(filePath);
Console.WriteLine("\n--- File contents ---");
Console.Write(allText);
// Read as array of lines
string[] readLines = File.ReadAllLines(filePath);
Console.WriteLine(````````\nTotal lines: ${readLines.Length}````````);
// File metadata via FileInfo
var info = new FileInfo(filePath);
Console.WriteLine(````````File size : ${info.Length} bytes````````);
Console.WriteLine(````````Extension : ${info.Extension}````````);
File.Delete(filePath);
Console.WriteLine("File deleted.");File.WriteAllLines creates or overwrites; AppendAllText adds without overwriting. FileInfo caches the path and exposes metadata properties without extra File.* calls.
using System;
using System.IO;
using System.Text;
string path = Path.Combine(Path.GetTempPath(), "numbers.csv");
// Create a sample CSV file
var sb = new StringBuilder();
sb.AppendLine("Name,Score");
sb.AppendLine("Alice,95");
sb.AppendLine("Bob,82");
sb.AppendLine("Carol,91");
sb.AppendLine("Dave,78");
File.WriteAllText(path, sb.ToString());
// Process line-by-line with StreamReader (efficient for large files)
int lineNum = 0;
double total = 0;
int count = 0;
using (var reader = new StreamReader(path))
{
string? line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (lineNum == 1) { Console.WriteLine(````````Header: ${line}````````); continue; }
var parts = line.Split(',');
if (parts.Length == 2 && double.TryParse(parts[1], out double score))
{
Console.WriteLine(```````` ${parts[0],-8} ${score}````````);
total += score;
count++;
}
}
}
Console.WriteLine(````````Average score: ${total / count:F1}````````);
File.Delete(path);StreamReader.ReadLine() reads one line at a time, keeping memory usage constant regardless of file size. The 'using' statement guarantees the file handle is closed even if an exception occurs mid-processing.
using System;
using System.IO;
// Create a temp directory structure for the demo
string root = Path.Combine(Path.GetTempPath(), "demo_dir");
Directory.CreateDirectory(Path.Combine(root, "src"));
Directory.CreateDirectory(Path.Combine(root, "docs"));
File.WriteAllText(Path.Combine(root, "src", "Program.cs"), "// main");
File.WriteAllText(Path.Combine(root, "src", "Helper.cs"), "// helper");
File.WriteAllText(Path.Combine(root, "docs", "readme.txt"), "Read me");
File.WriteAllText(Path.Combine(root, "docs", "notes.txt"), "Notes");
File.WriteAllText(Path.Combine(root, "build.log"), "Build ok");
// Find all C# files recursively
string[] csFiles = Directory.GetFiles(root, "*.cs", SearchOption.AllDirectories);
Console.WriteLine(````````C# files found: ${csFiles.Length}````````);
foreach (var f in csFiles)
Console.WriteLine(```````` ${Path.GetRelativePath(root, f)}````````);
// Find all text files in the docs folder only
string[] txtFiles = Directory.GetFiles(Path.Combine(root, "docs"), "*.txt");
Console.WriteLine(````````\nText files in docs: ${txtFiles.Length}````````);
foreach (var f in txtFiles)
Console.WriteLine(```````` ${Path.GetFileName(f)}````````);
// List subdirectories
string[] dirs = Directory.GetDirectories(root);
Console.WriteLine(````````\nSubdirectories: ${string.Join(", ", Array.ConvertAll(dirs, Path.GetFileName))}````````);
// Clean up
Directory.Delete(root, recursive: true);
Console.WriteLine("Cleaned up.");SearchOption.AllDirectories enables recursive search. Path.GetRelativePath produces platform-correct relative paths. Directory.Delete with recursive:true removes a directory tree in one call.
Quick Quiz
1. Which method should you use to read a large text file line-by-line without loading it entirely into memory?
2. What is the primary advantage of using Path.Combine() over string concatenation for file paths?
3. What does Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories) do?
Was this lesson helpful?