C# FileStream
last modified April 20, 2025
This tutorial explains how to use the FileStream class in C# to perform file I/O operations. FileStream provides a stream for file operations, supporting both synchronous and asynchronous read/write operations.
The FileStream class derives from the Stream class and provides access to standard input, output, and error streams. It allows reading from and writing to files at byte level.
FileStream
is useful for working with binary files or when
you need fine-grained control over file operations. It supports random
access to file content through seeking.
Basic FileStream Example
This example demonstrates basic file operations with FileStream. We create a file, write data to it, and then read the data back.
using System; using System.IO; using System.Text; class Program { static void Main() { // Write to file using (FileStream fs = new FileStream("test.txt", FileMode.Create)) { byte[] data = Encoding.UTF8.GetBytes("Hello FileStream!"); fs.Write(data, 0, data.Length); } // Read from file using (FileStream fs = new FileStream("test.txt", FileMode.Open)) { byte[] buffer = new byte[fs.Length]; fs.Read(buffer, 0, buffer.Length); string text = Encoding.UTF8.GetString(buffer); Console.WriteLine(text); } } }
The program creates a file named "test.txt" and writes a string to it. Then it reads the content back and displays it. The FileStream constructor takes the file path and FileMode as parameters. FileMode.Create specifies that a new file should be created.
When writing, we convert the string to bytes using UTF8 encoding. The Write method writes the byte array to the file. For reading, we create a byte array with the same length as the file. The Read method fills this array with file content, which we then convert back to a string.
Appending to a File
This example shows how to append data to an existing file using FileStream with FileMode.Append.
using System; using System.IO; using System.Text; class Program { static void Main() { string filePath = "log.txt"; // Append first message using (FileStream fs = new FileStream(filePath, FileMode.Append)) { byte[] data = Encoding.UTF8.GetBytes("First log entry\n"); fs.Write(data, 0, data.Length); } // Append second message using (FileStream fs = new FileStream(filePath, FileMode.Append)) { byte[] data = Encoding.UTF8.GetBytes("Second log entry\n"); fs.Write(data, 0, data.Length); } // Display file content Console.WriteLine(File.ReadAllText(filePath)); } }
The example demonstrates how to add content to the end of an existing file. FileMode.Append automatically positions the stream at the end of the file. This is useful for log files or when you need to add new data without overwriting existing content.
Each time we open the file with FileMode.Append, the write position is set to the end. The example writes two messages sequentially and then displays the complete file content. Note that we need to include newline characters explicitly when writing text lines.
Reading and Writing Binary Data
FileStream is commonly used for binary file operations. This example shows how to write and read primitive data types.
using System; using System.IO; class Program { static void Main() { string filePath = "data.bin"; // Write binary data using (FileStream fs = new FileStream(filePath, FileMode.Create)) { byte[] intBytes = BitConverter.GetBytes(42); byte[] doubleBytes = BitConverter.GetBytes(3.14159); byte[] boolBytes = BitConverter.GetBytes(true); fs.Write(intBytes, 0, intBytes.Length); fs.Write(doubleBytes, 0, doubleBytes.Length); fs.Write(boolBytes, 0, boolBytes.Length); } // Read binary data using (FileStream fs = new FileStream(filePath, FileMode.Open)) { byte[] buffer = new byte[4]; // Size of int fs.Read(buffer, 0, 4); int intValue = BitConverter.ToInt32(buffer, 0); fs.Read(buffer, 0, 8); double doubleValue = BitConverter.ToDouble(buffer, 0); fs.Read(buffer, 0, 1); bool boolValue = BitConverter.ToBoolean(buffer, 0); Console.WriteLine($"Int: {intValue}"); Console.WriteLine($"Double: {doubleValue}"); Console.WriteLine($"Boolean: {boolValue}"); } } }
The example writes an integer, a double, and a boolean to a binary file. We use BitConverter to convert these values to byte arrays. When reading, we must know the exact size and order of the data types in the file.
For reading, we create a buffer of appropriate size for each data type. The BitConverter class helps convert the byte arrays back to their original types. Note that we need to handle different sizes for different data types (4 bytes for int, 8 bytes for double, 1 byte for bool).
Random File Access
FileStream supports random access to file content through the Seek method. This example demonstrates reading from and writing to specific positions.
using System; using System.IO; using System.Text; class Program { static void Main() { string filePath = "random.bin"; // Create and initialize file using (FileStream fs = new FileStream(filePath, FileMode.Create)) { // Fill with 100 bytes of value 0 for (int i = 0; i < 100; i++) { fs.WriteByte(0); } } // Modify specific positions using (FileStream fs = new FileStream(filePath, FileMode.Open)) { // Write at position 10 fs.Seek(10, SeekOrigin.Begin); fs.WriteByte(255); // Write at position 50 from current position (now 11) fs.Seek(39, SeekOrigin.Current); fs.WriteByte(128); // Write at position 90 from end (file length is 100) fs.Seek(-10, SeekOrigin.End); fs.WriteByte(64); } // Read modified bytes using (FileStream fs = new FileStream(filePath, FileMode.Open)) { fs.Seek(10, SeekOrigin.Begin); Console.WriteLine("Position 10: " + fs.ReadByte()); fs.Seek(50, SeekOrigin.Begin); Console.WriteLine("Position 50: " + fs.ReadByte()); fs.Seek(90, SeekOrigin.Begin); Console.WriteLine("Position 90: " + fs.ReadByte()); } } }
The example first creates a file filled with 100 zero bytes. Then it modifies specific positions using Seek. SeekOrigin specifies the reference point for positioning: Begin, Current, or End of file.
We demonstrate all three SeekOrigin values. The first write is at position 10 from the beginning. The second moves 39 bytes from the current position (after writing 255 at position 10). The third seeks 10 bytes back from the end of file. Finally, we verify the changes by reading from these positions.
FileStream with Buffering
This example shows how to improve performance by using buffering with FileStream. The constructor allows specifying buffer size.
using System; using System.Diagnostics; using System.IO; using System.Text; class Program { static void Main() { string filePath = "largefile.bin"; int bufferSize = 4096; // 4KB buffer int fileSize = 10 * 1024 * 1024; // 10MB // Write without buffering var sw = Stopwatch.StartNew(); using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 1)) { for (int i = 0; i < fileSize; i++) { fs.WriteByte((byte)(i % 256)); } } Console.WriteLine($"Unbuffered write: {sw.ElapsedMilliseconds}ms"); // Write with buffering sw.Restart(); using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize)) { for (int i = 0; i < fileSize; i++) { fs.WriteByte((byte)(i % 256)); } } Console.WriteLine($"Buffered write: {sw.ElapsedMilliseconds}ms"); File.Delete(filePath); } }
The example compares performance of buffered vs unbuffered writes. We create a 10MB file by writing individual bytes. The first FileStream uses a 1-byte buffer (effectively unbuffered), while the second uses a 4KB buffer.
The performance difference can be significant, especially for many small writes. The bufferSize parameter in the FileStream constructor specifies the size of the internal buffer. Larger buffers reduce the number of actual disk operations by caching data in memory.
Asynchronous File Operations
FileStream supports asynchronous operations for better performance in I/O-bound scenarios. This example shows async read/write.
using System; using System.IO; using System.Text; using System.Threading.Tasks; class Program { static async Task Main() { string filePath = "async.txt"; // Asynchronous write using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) { byte[] data = Encoding.UTF8.GetBytes("This is async file operation\n"); await fs.WriteAsync(data, 0, data.Length); await fs.FlushAsync(); } // Asynchronous read using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, true)) { byte[] buffer = new byte[fs.Length]; await fs.ReadAsync(buffer, 0, buffer.Length); string content = Encoding.UTF8.GetString(buffer); Console.WriteLine(content); } } }
The example demonstrates asynchronous file operations using WriteAsync and ReadAsync. Note the Main method is marked as async and returns Task. The FileStream constructor includes the useAsync parameter set to true.
Asynchronous operations don't block the calling thread while waiting for I/O to complete. This is especially valuable in GUI applications or services handling multiple requests. The await keyword simplifies working with asynchronous operations by automatically handling continuations.
FileStream with FileShare Options
This example demonstrates how FileShare options control access to files when multiple processes need to work with the same file.
using System; using System.IO; using System.Threading; class Program { static void Main() { string filePath = "shared.txt"; // First process opens file for writing with FileShare.Read var fs1 = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read); fs1.WriteByte(65); // Write 'A' // Second process can open for reading using (var fs2 = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { Console.WriteLine("Second process read: " + fs2.ReadByte()); } // Try to open another writer (will fail) try { var fs3 = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.Read); Console.WriteLine("This won't be printed"); } catch (IOException ex) { Console.WriteLine("Expected error: " + ex.Message); } fs1.Close(); File.Delete(filePath); } }
The example shows how FileShare options affect file access. The first FileStream opens the file for writing with FileShare.Read, allowing other processes to read the file simultaneously.
The second FileStream successfully opens the file for reading. However, an attempt to open another writer fails because the first writer only allows shared reading. FileShare options help coordinate access to shared files among multiple processes.
Source
FileStream Class Documentation
This tutorial covered file I/O operations in C# with FileStream, including basic operations, binary data, random access, buffering, async operations, and file sharing.
Author
List all C# tutorials.