The System.IO namespace provides the building blocks for reading and writing
data: abstract streams, concrete file and memory streams, text readers and writers, and
helper types for working with paths and directories.
11.1 The Stream Abstraction
System.IO.Stream is the abstract base for all I/O streams. Key members:
Read(byte[] buf, int offset, int count) — read bytes
Write(byte[] buf, int offset, int count) — write bytes
Flush() — force buffered data to the underlying sink
ReadAsync / WriteAsync — async overloads (preferred for I/O-bound work)
CanRead, CanWrite, CanSeek, Length, Position — properties
Concrete stream types include:
FileStream — file I/O
MemoryStream — in-memory byte buffer
NetworkStream — TCP socket I/O
GZipStream / DeflateStream — compression wrappers
CryptoStream — encryption/decryption wrapper
BufferedStream — adds buffering to an unbuffered stream
Always dispose streams — use a using statement or declaration:
using var fs = new FileStream("data.bin", FileMode.Open, FileAccess.Read);
byte[] buf = new byte[fs.Length];
int bytesRead = fs.Read(buf, 0, buf.Length);
11.2 FileStream
FileStream accepts a FileMode and optional
FileAccess and FileShare arguments:
FileMode
Behavior
Open
Open existing; exception if absent
OpenOrCreate
Open if present, create if absent
Create
Create new (truncate if exists)
CreateNew
Create new; exception if exists
Append
Open/create; position at end; no seek before end
Truncate
Open existing and truncate to zero bytes
// Write binary data
using var ws = new FileStream("out.bin", FileMode.Create, FileAccess.Write);
ws.Write(BitConverter.GetBytes(42)); // write int as 4 bytes
ws.Write(BitConverter.GetBytes(3.14)); // write double as 8 bytes
11.3 Text Readers and Writers
StreamReader and StreamWriter wrap any stream with character
encoding (default UTF-8). They provide line-oriented methods:
// Write text
using var sw = new StreamWriter("log.txt", append: true);
sw.WriteLine($"[{DateTime.Now:HH:mm:ss}] started");
// Read text
using var sr = new StreamReader("log.txt");
string? line;
while ((line = sr.ReadLine()) is not null)
Console.WriteLine(line);
Convenience helpers on the File static class avoid manual
stream management for simple cases:
// one-shot reads
string all = File.ReadAllText("data.txt");
string[] lines = File.ReadAllLines("data.txt");
byte[] bytes = File.ReadAllBytes("data.bin");
// one-shot writes
File.WriteAllText("out.txt", "hello\n");
File.AppendAllText("out.txt", "world\n");
// lazy line-by-line (avoids loading entire file)
foreach (string ln in File.ReadLines("big.txt"))
Process(ln);
StringReader and StringWriter apply the same reader/writer
interface to in-memory strings, making it easy to unit-test code that works with a
TextReader or TextWriter without touching the filesystem.
11.4 BinaryReader and BinaryWriter
BinaryWriter writes primitive values in their binary representation.
BinaryReader reads them back. They are type-safe and endian-consistent
(always little-endian on .NET):
// write
using var bw = new BinaryWriter(File.Create("data.bin"));
bw.Write(42);
bw.Write(3.14);
bw.Write("hello"); // length-prefixed UTF-8 string
// read
using var br = new BinaryReader(File.OpenRead("data.bin"));
int n = br.ReadInt32();
double d = br.ReadDouble();
string s = br.ReadString();
11.5 MemoryStream
MemoryStream is backed by a resizable byte array. Use it to build up a byte
sequence in memory and then use its ToArray() or GetBuffer()
method to get the result, or seek back to the beginning and wrap it with another reader:
using var ms = new MemoryStream();
using (var bw = new BinaryWriter(ms, Encoding.UTF8, leaveOpen: true)) {
bw.Write(100);
bw.Write("test");
}
ms.Position = 0;
using var br = new BinaryReader(ms);
Console.WriteLine(br.ReadInt32()); // 100
Console.WriteLine(br.ReadString()); // test
11.6 Path and Directory Helpers
System.IO.Path manipulates path strings without touching the filesystem.
Directory and File operate on the filesystem:
string dir = Path.GetDirectoryName("C:/data/log.txt")!; // C:/data
string name = Path.GetFileName("C:/data/log.txt"); // log.txt
string ext = Path.GetExtension("C:/data/log.txt"); // .txt
string full = Path.Combine("C:/data", "sub", "out.txt"); // cross-platform join
Directory.CreateDirectory("output/2026");
bool exists = File.Exists("config.json");
string[] files = Directory.GetFiles(".", "*.cs", SearchOption.AllDirectories);
Use Path.Combine rather than string concatenation so that your code works
on both Windows (backslash) and Unix (forward slash) without modification.
11.7 Async File I/O
File I/O is I/O-bound. Prefer async APIs so the calling thread is not blocked while
the OS moves data:
async Task WriteLogAsync(string path, string message) {
await using var sw = new StreamWriter(path, append: true);
await sw.WriteLineAsync($"[{DateTime.UtcNow:O}] {message}");
}
async Task<string> ReadFileAsync(string path) =>
await File.ReadAllTextAsync(path);
Note await using: when a disposable type also implements
IAsyncDisposable, use await using to await the async dispose,
which flushes the buffer before releasing the file handle.
11.8 Epilogue
This chapter covered the stream abstraction, file and memory streams, text and binary
readers/writers, path utilities, and async I/O. The next chapter examines
System.Collections.Generic.