Skip to main content

C# FileStream

FileStream is a fundamental class in C# that provides a powerful way to read from, write to, and manipulate files. It's an essential component of file handling operations in C# applications, allowing developers to work directly with files at a byte level.

Introduction to FileStream

The FileStream class in C# represents a stream of bytes that is used specifically for file operations. It's found in the System.IO namespace and serves as a bridge between your application and the file system. Unlike some higher-level file handling classes, FileStream gives you fine-grained control over file operations.

Key characteristics of FileStream include:

  • Byte-level access to file data
  • Support for synchronous and asynchronous operations
  • Ability to specify access modes (read, write, read/write)
  • Control over file sharing permissions
  • Buffer management for improved performance

Basic Syntax and Usage

To use FileStream in your C# application, you first need to include the System.IO namespace:

csharp
using System;
using System.IO;

Creating a FileStream

Here's the basic syntax for creating a FileStream object:

csharp
FileStream fileStream = new FileStream(path, mode, access, share);

Parameters explained:

  • path: The file path
  • mode: How to open or create the file (FileMode enum)
  • access: Read, write, or both (FileAccess enum)
  • share: How the file can be shared (FileShare enum)

FileStream Operations

Writing to a File

Let's look at a simple example of writing bytes to a file:

csharp
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string filePath = "example.txt";
string textToWrite = "Hello, FileStream!";
byte[] byteArray = Encoding.UTF8.GetBytes(textToWrite);

// Create a FileStream to write to the file
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
// Write the bytes to the file
fileStream.Write(byteArray, 0, byteArray.Length);

Console.WriteLine($"Successfully wrote {byteArray.Length} bytes to {filePath}");
} // FileStream is automatically closed here due to using statement
}
}

Output:

Successfully wrote 18 bytes to example.txt

In this example:

  1. We convert a string to a byte array
  2. Create a FileStream with write access
  3. Write the byte array to the file
  4. The using statement ensures the filestream is properly closed when done

Reading from a File

Now let's read the file we just created:

csharp
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string filePath = "example.txt";

// Create a FileStream to read from the file
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
// Create a byte array to store the read data
byte[] byteArray = new byte[fileStream.Length];

// Read the bytes from the file
int bytesRead = fileStream.Read(byteArray, 0, byteArray.Length);

// Convert bytes to string and display
string textRead = Encoding.UTF8.GetString(byteArray);

Console.WriteLine($"Read {bytesRead} bytes from {filePath}");
Console.WriteLine($"Content: {textRead}");
}
}
}

Output:

Read 18 bytes from example.txt
Content: Hello, FileStream!

In this example:

  1. We open the file in read mode
  2. Create a byte array sized to the file length
  3. Read the entire file into the byte array
  4. Convert the bytes back to a string
  5. Display the content

FileStream Properties and Methods

FileStream provides several useful properties and methods:

Key Properties

  • Length: Gets the length of the stream in bytes
  • Position: Gets or sets the current position in the stream
  • CanRead: Indicates if the stream supports reading
  • CanWrite: Indicates if the stream supports writing
  • CanSeek: Indicates if the stream supports seeking

Important Methods

  • Read(): Reads a block of bytes from the stream
  • Write(): Writes a block of bytes to the stream
  • Seek(): Sets the position in the stream
  • Flush(): Clears buffers and writes all data to the file
  • Close(): Closes the stream and releases resources

FileMode, FileAccess, and FileShare Enums

When creating a FileStream, you need to specify how to open the file, what kind of access you need, and how the file should be shared:

FileMode Options

csharp
// Common FileMode options
FileMode.Create // Creates a new file (overwrites if exists)
FileMode.CreateNew // Creates a new file (throws exception if exists)
FileMode.Open // Opens an existing file (throws exception if not exists)
FileMode.OpenOrCreate // Opens if exists, creates if not
FileMode.Append // Opens and seeks to the end, or creates
FileMode.Truncate // Opens and clears content (file must exist)

FileAccess Options

csharp
// FileAccess options
FileAccess.Read // Read access
FileAccess.Write // Write access
FileAccess.ReadWrite // Both read and write access

FileShare Options

csharp
// Common FileShare options
FileShare.None // No sharing (locks file)
FileShare.Read // Other processes can read
FileShare.Write // Other processes can write
FileShare.ReadWrite // Other processes can read and write

Advanced FileStream Usage

Working with File Positions

FileStream allows you to move around within a file using the Seek method and Position property:

csharp
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string filePath = "positions.txt";

// Create and write to the file
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
byte[] data = Encoding.UTF8.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
fs.Write(data, 0, data.Length);
}

// Read specific portions of the file
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
// Create a small buffer for reading
byte[] buffer = new byte[5];

// Read the first 5 characters
fs.Read(buffer, 0, 5);
Console.WriteLine("First 5 characters: " + Encoding.UTF8.GetString(buffer));

// Move to position 10
fs.Position = 10;

// Read 5 characters starting from position 10
fs.Read(buffer, 0, 5);
Console.WriteLine("5 characters from position 10: " + Encoding.UTF8.GetString(buffer));

// Move 5 positions forward from current position
fs.Seek(5, SeekOrigin.Current);

// Read 5 more characters
fs.Read(buffer, 0, 5);
Console.WriteLine("5 characters after seeking forward 5: " + Encoding.UTF8.GetString(buffer));
}
}
}

Output:

First 5 characters: ABCDE
5 characters from position 10: KLMNO
5 characters after seeking forward 5: UVWXY

Asynchronous File Operations

For better performance in applications, especially when dealing with large files, you can use the asynchronous methods:

csharp
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
string filePath = "async_example.txt";
string textToWrite = "This is an asynchronous file operation example.";
byte[] byteArray = Encoding.UTF8.GetBytes(textToWrite);

// Write asynchronously
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write,
FileShare.None, bufferSize: 4096, useAsync: true))
{
Console.WriteLine("Writing to file asynchronously...");
await fileStream.WriteAsync(byteArray, 0, byteArray.Length);
Console.WriteLine("Write operation completed.");
}

// Read asynchronously
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read,
FileShare.Read, bufferSize: 4096, useAsync: true))
{
byte[] buffer = new byte[fileStream.Length];
Console.WriteLine("Reading from file asynchronously...");
await fileStream.ReadAsync(buffer, 0, buffer.Length);
string content = Encoding.UTF8.GetString(buffer);
Console.WriteLine($"Read completed. Content: {content}");
}
}
}

Output:

Writing to file asynchronously...
Write operation completed.
Reading from file asynchronously...
Read completed. Content: This is an asynchronous file operation example.

Buffered File Operations

You can improve performance by setting an appropriate buffer size:

csharp
// Create a FileStream with a 8KB buffer
using (FileStream fs = new FileStream("large_file.dat", FileMode.Create,
FileAccess.Write, FileShare.None, bufferSize: 8192))
{
// File operations here will use the specified buffer
}

Real-World Application: Simple File Copy

Let's create a file copying utility that demonstrates FileStream usage in a practical scenario:

csharp
using System;
using System.IO;
using System.Diagnostics;

class Program
{
static void Main()
{
string sourceFile = "source.txt";
string destinationFile = "destination.txt";

// Create a sample source file if it doesn't exist
if (!File.Exists(sourceFile))
{
using (FileStream fs = new FileStream(sourceFile, FileMode.Create, FileAccess.Write))
{
byte[] data = new byte[1024 * 1024]; // 1MB of data
for (int i = 0; i < data.Length; i++)
{
data[i] = (byte)(i % 256);
}
fs.Write(data, 0, data.Length);
}
Console.WriteLine($"Created sample file: {sourceFile} (1MB)");
}

Console.WriteLine($"Copying {sourceFile} to {destinationFile}...");

// Start timing
Stopwatch stopwatch = Stopwatch.StartNew();

// Copy the file using FileStream
using (FileStream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
using (FileStream destinationStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write))
{
byte[] buffer = new byte[4096]; // 4KB buffer
int bytesRead;
long totalBytes = 0;

// Read and write in chunks until end of file
while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
{
destinationStream.Write(buffer, 0, bytesRead);
totalBytes += bytesRead;
}

stopwatch.Stop();

Console.WriteLine($"Copy completed successfully!");
Console.WriteLine($"Copied {totalBytes:N0} bytes in {stopwatch.ElapsedMilliseconds} ms");
Console.WriteLine($"Transfer rate: {(totalBytes / 1024.0 / 1024.0) / (stopwatch.ElapsedMilliseconds / 1000.0):F2} MB/s");
}
}
}

Output (will vary based on system performance):

Created sample file: source.txt (1MB)
Copying source.txt to destination.txt...
Copy completed successfully!
Copied 1,048,576 bytes in 23 ms
Transfer rate: 43.85 MB/s

This example:

  1. Creates a 1MB sample file if it doesn't exist
  2. Opens the source file for reading
  3. Creates the destination file for writing
  4. Uses a 4KB buffer to read and write data in chunks
  5. Measures and reports the performance of the copy operation

Common FileStream Errors and How to Handle Them

When working with FileStream, you might encounter various exceptions:

csharp
using System;
using System.IO;

class Program
{
static void Main()
{
try
{
// Try to open a non-existent file
using (FileStream fs = new FileStream("non_existent.txt", FileMode.Open))
{
// Code here won't execute if the file doesn't exist
}
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"File not found: {ex.Message}");
}

try
{
// Try to write to a read-only file
string readOnlyFile = "readonly.txt";

// Create and mark as read-only
File.WriteAllText(readOnlyFile, "This is a read-only file");
File.SetAttributes(readOnlyFile, FileAttributes.ReadOnly);

using (FileStream fs = new FileStream(readOnlyFile, FileMode.Open, FileAccess.Write))
{
// Code here won't execute due to access denied
}
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"Access denied: {ex.Message}");
}
finally
{
// Clean up
if (File.Exists("readonly.txt"))
{
File.SetAttributes("readonly.txt", FileAttributes.Normal);
File.Delete("readonly.txt");
}
}
}
}

Output:

File not found: Could not find file 'non_existent.txt'.
Access denied: Access to the path 'readonly.txt' is denied.

Best Practices for Using FileStream

  1. Always use the using statement - This ensures that the FileStream is properly closed and resources are released, even if exceptions occur.

  2. Choose the right buffer size - For large files, a larger buffer (e.g., 8KB or more) can improve performance.

  3. Use asynchronous methods - For UI applications or web servers, use ReadAsync and WriteAsync to prevent blocking.

  4. Be specific with FileMode, FileAccess, and FileShare - Only request the permissions you need.

  5. Handle exceptions properly - File operations are prone to various exceptions (file not found, access denied, etc.).

  6. Consider using higher-level classes when appropriate - For simple text file operations, File, StreamReader, and StreamWriter might be more convenient.

Summary

FileStream is a powerful class in C# that provides direct access to files at a byte level. It offers fine-grained control over file operations, including positioning, buffering, and sharing. Understanding FileStream is essential for efficient file handling in C# applications, especially when working with binary files or requiring precise control over how files are accessed.

Key points to remember:

  • FileStream works with bytes, not text directly
  • It supports both synchronous and asynchronous operations
  • You can control file access, sharing, and buffering
  • Always use proper exception handling for file operations
  • The using statement ensures resources are properly released

Additional Resources

Exercises

  1. Create a program that uses FileStream to append data to an existing file without overwriting its contents.

  2. Build a simple file encryption/decryption tool using FileStream that reads a file byte by byte, performs a simple XOR operation with a key, and writes the result to a new file.

  3. Implement a file comparison tool that uses two FileStream instances to compare files byte by byte and report differences.

  4. Create a program that uses FileStream to read a large file in chunks and calculate its MD5 or SHA-256 hash.

  5. Implement a simple file splitter that breaks a large file into smaller chunks of a specified size using FileStream.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)