Skip to main content

Command Palette

Search for a command to run...

A Guide to File, FileStream, and BufferedStream

How to use, Performance, Overhead and Best Practice

Updated
4 min read

The File Class: For Convenience

The File class provides static methods (File.ReadAllText, File.WriteAllBytes, etc.) for simple, "one-shot" file operations. Each method handles the entire process for you: opening the file, performing the action, and immediately closing it.

Example: Writing and Reading a File

This code writes a line of text to a file and then reads it back.

string filePath = "settings.txt";
string content = "EnableHighPerformance=true";

// --- Writing the entire file in one call ---
File.WriteAllText(filePath, content);

// --- Reading the entire file in one call ---
string readContent = File.ReadAllText(filePath);

Console.WriteLine($"Content read from file: {readContent}");

Explanation

  • File.WriteAllText(filePath, content);

    • This static method is a self-contained transaction. Behind the scenes, it creates a FileStream and a buffered StreamWriter, writes your content, and immediately closes everything for you.
  • string readContent = File.ReadAllText(filePath);

    • Similarly, this method performs the entire read operation in one go using a FileStream and a buffered StreamReader internally.

Performance, Practice, and Overhead

  • Performance: Excellent for single operations. However, performance is very poor if used repeatedly in a loop (e.g., calling File.AppendAllText 1,000 times).

  • Overhead: The overhead comes from the repeated work of opening and closing the file handle for each call. This is expensive and should be avoided in loops.

  • Best Practice: Use the File class for simple, atomic operations where the entire file can comfortably fit into memory. It's the cleanest and safest choice for straightforward tasks.


FileStream: For Control and Performance

A FileStream is the fundamental object for interacting with files, giving you a direct "pipe" to read and write raw bytes. Use it when you need fine-grained control or are performing multiple operations on the same file.

Example: Appending Multiple Lines Efficiently

This code efficiently appends 1,000 log entries to a file by keeping the stream open.

string logPath = "activity.log";

// Open the file stream once, ready to append
using (FileStream fs = new FileStream(logPath, FileMode.Append))
using (StreamWriter writer = new StreamWriter(fs))
{
    // Perform multiple write operations on the open stream
    for (int i = 0; i < 1000; i++)
    {
        writer.WriteLine($"Log entry #{i + 1} at {DateTime.Now}");
    }
}

Explanation

  • using (FileStream fs = new FileStream(logPath, FileMode.Append))

    • We manually create a FileStream object, giving us direct control. FileMode.Append places the cursor at the end of the file, ready for writing.

    • The using statement is crucial for automatically closing and disposing of the stream.

  • using (StreamWriter writer = new StreamWriter(fs))

    • We wrap the byte-based FileStream with a text-based StreamWriter, which acts as a translator from string to bytes.
  • writer.WriteLine(...)

    • Because the file stream is kept open, writing 1,000 lines inside the loop is extremely fast.

Performance, Practice, and Overhead

  • Performance: Superior for any task involving multiple operations on the same file. The performance of FileStream in .NET 6+ is highly optimized with excellent internal buffering.

  • Overhead: The main "overhead" is the increased code verbosity compared to the File class. There is a small, one-time cost to open the file handle, but this is negligible when performing many operations.

  • Best Practice: Use FileStream for large files, processing files in chunks, or when you need to perform a series of actions (read, write, seek) on the same open file. Always wrap it in a using statement.


BufferedStream: The Performance Wrapper

A BufferedStream is a wrapper that adds an in-memory buffer to another stream. Its goal is to improve performance by reducing the number of slow I/O calls to the underlying resource (like a disk).

Example: Explicitly Buffering a File Write

This code wraps a FileStream with an 8 KB BufferedStream.

string dataPath = "data.bin";

using (FileStream fs = new FileStream(dataPath, FileMode.Create))
{
    // Wrap the FileStream with an 8 KB (8192 bytes) buffer
    using (BufferedStream bs = new BufferedStream(fs, 8192))
    {
        // Write some bytes to the buffer
        for (int i = 0; i < 10000; i++)
        {
            bs.WriteByte((byte)i);
        }
    } // The buffer is automatically flushed (written to disk) here when disposed
}

Explanation

  • using (BufferedStream bs = new BufferedStream(fs, 8192))

    • We wrap our FileStream (fs) and specify an explicit buffer size of 8 KB.
  • bs.WriteByte((byte)i);

    • This WriteByte call writes to the fast in-memory buffer, not directly to the disk.

    • The BufferedStream only writes to the FileStream when its buffer is full or when the stream is closed, turning thousands of small, slow writes into a few large, efficient ones.

Performance, Practice, and Overhead

  • Performance: Can provide a significant boost for streams that are unbuffered. However, with the modern FileStream in .NET 6+, the performance gain is often negligible or non-existent because FileStream already has excellent built-in buffering.

  • Overhead: The overhead is the memory allocation for the buffer and the extra layer of abstraction. If the underlying stream is already well-buffered, this just adds a slight, unnecessary performance cost.

  • Best Practice: In modern .NET, avoid using BufferedStream with FileStream unless you have a specific, benchmarked reason. Its primary use case today is for wrapping streams that you know are unbuffered (e.g., some NetworkStream implementations or custom hardware streams).