Early computing systems relied exclusively on ASCII for character representation. As software began targeting global markets, support for extended character sets became necessary, leading to the adoption of Unicode and its variable-length encoding schemes like UTF-8 and UTF-32. In Windows environments, text files typically begin with a Byte Order Mark (BOM) that signals the specific encoding format. Standard text editors automatically parse these initial bytes to render characters correctly.
The .NET runtime abstracts this complexity. When opening a file for reading, the framework either applies a developer-specified encoding or automatically inspects the BOM to determine the correct decoding strategy. During write operations, developers can explicitly declare an encoding. If omitted, the runtime defaults to UTF-8 when non-ASCII character are present, otherwise falling back to ASCII compatibility.
Reading Text Files
For small datasets, the File class provides synchronous methods that load content directly into memory. ReadAllText returns the complete file as a single string, while ReadAllLines splits the content at line boundaries and returns a string array. Both overloads accept an optional Encoding parameter.
using System;
using System.IO;
using System.Text;
// Load entire content into a single string variable
string fullContent = File.ReadAllText("data\\report.txt");
// Explicitly decode using ISO-8859-1
string latinContent = File.ReadAllText("data\\report.txt", Encoding.GetEncoding("ISO-8859-1"));
// Split file into an array where each element represents a line
string[] lineArray = File.ReadAllLines("data\\report.txt");
When handling large documents, loading everything into RAM is inefficient and can trigger memory pressure. The recommended approach is streaming via StreamReader. This class can be instantiated directly with a file path or wrapped around a FileStream for granular control over file access modes and sharing permissions.
// Direct instantiation with automatic BOM detection
using var readerAuto = new StreamReader("logs\\application.log");
// Explicit UTF-8 configuration
using var readerUtf8 = new StreamReader("logs\\application.log", Encoding.UTF8);
// Wrapping a FileStream for custom share/access flags
using var fileStream = new FileStream("logs\\application.log", FileMode.Open, FileAccess.Read, FileShare.Read);
using var readerStream = new StreamReader(fileStream, Encoding.UTF8);
Once instantiated, StreamReader exposes multiple consumption patterns: ReadLine() retrieves content sequentially, Read() processes individual characters, Read(char[], int, int) extracts buffered chunks, and ReadToEnd() drains the remaining stream. Always ensure resources are released via the using pattern or explicit disposal.
using var input = File.OpenText("config\\settings.txt");
string currentRow;
// Process sequentially until the end of the stream
while ((currentRow = input.ReadLine()) != null)
{
Console.WriteLine(currentRow.Trim());
}
Writing Text Files
Writing operations mirror the reading strategy. For straightforward tasks, File.WriteAllText and File.WriteAllLines handle serialization, file creation, and overwriting automatically.
string message = "System initialization complete.";
// Default encoding behavior
File.WriteAllText("output\\status.txt", message);
// Force ASCII encoding
File.WriteAllText("output\\status_ascii.txt", message, Encoding.ASCII);
string[] records = { "Entry Alpha", "Entry Beta", "Entry Gamma" };
File.WriteAllLines("output\\records.txt", records);
Note that these utility methods will generate the target path if it does not exist and will overwrite existing files without warning.
For continuous logging or high-volume data export, StreamWriter is the optimal choice. Its constructors support an append boolean flag to toggle between overwriting and appending modes.
// Create or overwrite mode
using var writerOverwrite = new StreamWriter("data\\cache.txt");
// Append mode with explicit UTF-8 encoding
using var writerAppend = new StreamWriter("data\\cache.txt", true, Encoding.UTF8);
// FileStream integration for custom access rules
using var outputStream = new FileStream("data\\cache.txt", FileMode.Create, FileAccess.Write);
using var writerFileStream = new StreamWriter(outputStream, Encoding.UTF8);
The writer accepts various input types, including single characters, raw strings, character arrrays, or formatted text. Calling Close() or exiting the using block automatically flushes the internal buffer and releases the underlying file handle.
string[] auditTrail = { "2023-11-15: Service started", "2023-11-15: Database connected" };
using var logWriter = new FileInfo("audit\\system.log").CreateText();
foreach (string entry in auditTrail)
{
logWriter.WriteLine($"[AUDIT] {entry}");
}