C# File System Operations
File Operations Overview
System.IO provides classes for file system operations. Most operations have both synchronous and async variants.
using System.IO;
// Check existence
bool exists = File.Exists("data.txt");
bool dirExists = Directory.Exists("logs");
// Simple read/write
string content = File.ReadAllText("config.json");
File.WriteAllText("output.txt", "Hello, World!");
// Read/write lines
string[] lines = File.ReadAllLines("data.csv");
File.WriteAllLines("output.csv", lines);
// Read/write bytes
byte[] bytes = File.ReadAllBytes("image.png");
File.WriteAllBytes("copy.png", bytes);
Path Operations
Always Use Path.Combine
Never concatenate paths with string operations. Path.Combine handles cross-platform separators correctly.
Use Path class for cross-platform path manipulation.
// Combine paths (handles separators correctly)
string fullPath = Path.Combine("folder", "subfolder", "file.txt");
// Get path components
string dir = Path.GetDirectoryName(fullPath); // folder/subfolder
string file = Path.GetFileName(fullPath); // file.txt
string name = Path.GetFileNameWithoutExtension(fullPath); // file
string ext = Path.GetExtension(fullPath); // .txt
// Change extension
string newPath = Path.ChangeExtension(fullPath, ".json");
// Get absolute path
string absolute = Path.GetFullPath("relative/path");
// Special folders
string temp = Path.GetTempPath();
string tempFile = Path.GetTempFileName(); // Creates empty temp file
// .NET 6+ - Path.Exists checks both files and directories
bool exists = Path.Exists("something");
Reading Files
Text Files
// Read all at once (small files)
string content = File.ReadAllText("file.txt");
string[] lines = File.ReadAllLines("file.txt");
// Read line by line (memory efficient)
foreach (string line in File.ReadLines("large.txt"))
{
ProcessLine(line);
}
// Async reading
string content = await File.ReadAllTextAsync("file.txt");
string[] lines = await File.ReadAllLinesAsync("file.txt");
// With specific encoding
string content = File.ReadAllText("file.txt", Encoding.UTF8);
Binary Files
byte[] data = File.ReadAllBytes("file.bin");
byte[] data = await File.ReadAllBytesAsync("file.bin");
// Using FileStream for large files
await using var stream = File.OpenRead("large.bin");
var buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer)) > 0)
{
ProcessChunk(buffer.AsSpan(0, bytesRead));
}
StreamReader
StreamReader vs ReadAllText
Use StreamReader for line-by-line processing of large files (memory efficient). Use File.ReadAllText for small files where you need all content at once.
using var reader = new StreamReader("file.txt");
// Read line by line
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
Console.WriteLine(line);
}
// Read to end
string all = await reader.ReadToEndAsync();
// With encoding
using var reader = new StreamReader("file.txt", Encoding.UTF8);
Writing Files
Text Files
// Write all at once
File.WriteAllText("file.txt", content);
File.WriteAllLines("file.txt", lines);
// Async writing
await File.WriteAllTextAsync("file.txt", content);
await File.WriteAllLinesAsync("file.txt", lines);
// Append
File.AppendAllText("log.txt", "New entry\n");
await File.AppendAllTextAsync("log.txt", "New entry\n");
File.AppendAllLines("log.txt", newLines);
Binary Files
File.WriteAllBytes("file.bin", data);
await File.WriteAllBytesAsync("file.bin", data);
// Using FileStream
await using var stream = File.Create("file.bin");
await stream.WriteAsync(data);
StreamWriter
// Create/overwrite
await using var writer = new StreamWriter("file.txt");
await writer.WriteLineAsync("First line");
await writer.WriteAsync("More text");
// Append
await using var writer = new StreamWriter("file.txt", append: true);
// With encoding and buffer
await using var writer = new StreamWriter("file.txt", Encoding.UTF8,
new FileStreamOptions { BufferSize = 4096 });
Directory Operations
// Create directory (creates parent directories too)
Directory.CreateDirectory("path/to/new/folder");
// List contents
string[] files = Directory.GetFiles("folder");
string[] dirs = Directory.GetDirectories("folder");
// Recursive search
string[] allCsFiles = Directory.GetFiles("src", "*.cs", SearchOption.AllDirectories);
// Enumerate (memory efficient for large directories)
foreach (string file in Directory.EnumerateFiles("folder", "*.txt"))
{
Console.WriteLine(file);
}
// Delete
Directory.Delete("folder"); // Must be empty
Directory.Delete("folder", recursive: true); // Delete all contents
// Move/rename
Directory.Move("old/path", "new/path");
FileInfo and DirectoryInfo
Object-oriented alternative with cached metadata.
// FileInfo
var file = new FileInfo("document.pdf");
if (file.Exists)
{
Console.WriteLine($"Size: {file.Length} bytes");
Console.WriteLine($"Created: {file.CreationTime}");
Console.WriteLine($"Modified: {file.LastWriteTime}");
file.CopyTo("backup.pdf", overwrite: true);
file.MoveTo("new/location.pdf");
// file.Delete();
}
// DirectoryInfo
var dir = new DirectoryInfo("logs");
foreach (FileInfo f in dir.EnumerateFiles("*.log"))
{
if (f.LastWriteTime < DateTime.Now.AddDays(-30))
{
f.Delete();
}
}
File Copy, Move, Delete
// Copy
File.Copy("source.txt", "dest.txt");
File.Copy("source.txt", "dest.txt", overwrite: true);
// Move (rename)
File.Move("old.txt", "new.txt");
File.Move("file.txt", "archive/file.txt", overwrite: true); // .NET 5+
// Delete
File.Delete("file.txt"); // No error if doesn't exist
// Replace (atomic on same volume)
File.Replace("new.txt", "target.txt", "backup.txt");
File Streams
// FileMode: Create, CreateNew, Open, OpenOrCreate, Append, Truncate
// FileAccess: Read, Write, ReadWrite
// FileShare: None, Read, Write, ReadWrite
await using var stream = new FileStream(
"file.bin",
FileMode.OpenOrCreate,
FileAccess.ReadWrite,
FileShare.Read,
bufferSize: 4096,
useAsync: true);
// Or simpler
await using var read = File.OpenRead("file.bin");
await using var write = File.OpenWrite("file.bin");
await using var create = File.Create("new.bin");
Copy Stream to Stream
await using var source = File.OpenRead("source.bin");
await using var dest = File.Create("dest.bin");
await source.CopyToAsync(dest);
Common Patterns
Safe File Writing
public async Task WriteFileSafelyAsync(string path, string content)
{
var tempPath = path + ".tmp";
var backupPath = path + ".bak";
await File.WriteAllTextAsync(tempPath, content);
if (File.Exists(path))
{
File.Replace(tempPath, path, backupPath);
}
else
{
File.Move(tempPath, path);
}
}
Watch for Changes
using var watcher = new FileSystemWatcher("folder")
{
Filter = "*.txt",
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
EnableRaisingEvents = true
};
watcher.Changed += (s, e) => Console.WriteLine($"Changed: {e.FullPath}");
watcher.Created += (s, e) => Console.WriteLine($"Created: {e.FullPath}");
watcher.Deleted += (s, e) => Console.WriteLine($"Deleted: {e.FullPath}");
watcher.Renamed += (s, e) => Console.WriteLine($"Renamed: {e.OldFullPath} -> {e.FullPath}");
Read Lines with Index
var numberedLines = File.ReadLines("file.txt")
.Select((line, index) => (Number: index + 1, Text: line));
foreach (var (number, text) in numberedLines)
{
Console.WriteLine($"{number}: {text}");
}
Error Handling
try
{
string content = File.ReadAllText(path);
}
catch (FileNotFoundException)
{
// File doesn't exist
}
catch (DirectoryNotFoundException)
{
// Directory doesn't exist
}
catch (UnauthorizedAccessException)
{
// Permission denied
}
catch (IOException ex)
{
// Other I/O error (file in use, disk full, etc.)
}
Version History
| Feature | Version | Significance |
|---|---|---|
| System.IO | .NET 1.0 | Core file operations |
| Async methods | .NET 4.5 | ReadAllTextAsync, etc. |
| File.Move overwrite | .NET 5 | Overwrite parameter |
| Path.Exists | .NET 6 | Check file or directory |
| RandomAccess | .NET 6 | High-perf file I/O |
Key Takeaways
Use Path.Combine: Never concatenate paths with string operations. Path.Combine handles cross-platform separators.
Prefer async for I/O: Use ReadAllTextAsync, WriteAllTextAsync, etc. for better scalability.
Use ReadLines for large files: It enumerates lazily instead of loading the entire file into memory.
Handle I/O exceptions: File operations can fail for many reasons. Always handle IOException and related exceptions.
Dispose streams: Always use using or await using with streams to ensure proper cleanup.
Consider file locking: Be aware of FileShare options when multiple processes might access the same file.
Found this guide helpful? Share it with your team:
Share on LinkedIn