C# provides several ways to open, read from, and write to files on the file system. The key classes that enable file I/O in C# are FileStream
, StreamReader
, and StreamWriter
from the System.IO
namespace. In this comprehensive guide, we will explore various examples of opening, reading, writing, and manipulating files in C# using these classes.
The FileStream Class
The FileStream
class provides the core functionality for reading from and writing to files. To open a file, you create a FileStream
object, specifying the file path and the mode you want to open the file in. Some common file modes are:
FileMode.Open
: Opens an existing file for reading/writing. An exception is thrown if the file does not exist.FileMode.Create
: Creates a new file. An exception is thrown if the file already exists.FileMode.Append
: Opens an existing file or creates a new file if it doesn‘t exist, for appending data to the end.FileMode.Truncate
: Opens an existing file and truncates its size to 0 bytes to overwrite it.
Here is an example of opening an existing file for reading:
FileStream file = new FileStream("data.txt", FileMode.Open);
Once you have a FileStream
, you can call methods like Read()
and Write()
to access the file contents.
Reading Files with StreamReader
For reading text files, the StreamReader
class provides useful high-level functionality. To use it, wrap a FileStream
in a StreamReader
and call ReadLine()
and other methods to access file contents.
Here is an example:
using (StreamReader reader = new StreamReader("data.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
This loops through each line in the text file and prints it out. The using
block ensures the reader is properly disposed.
Writing Files with StreamWriter
Similarly, for writing text files, use the StreamWriter
class. You can wrap a FileStream
in it or construct it directly with a file path.
Here‘s an example:
using (StreamWriter writer = new StreamWriter("log.txt"))
{
writer.WriteLine("Activity log");
writer.WriteLine(DateTime.Now);
}
This writes a couple lines of text to the file. Like StreamReader
, it handles text encodings and provides helpful methods like WriteLine()
.
Appending to Existing Files
To add data to the end of an existing file, open the stream with FileMode.Append
:
FileStream stream = new FileStream("log.txt", FileMode.Append);
Then call Write()
methods as usual to append data.
Reading and Writing Binary Data
For binary data (bytes rather than text), use FileStream
‘s ReadByte()
and WriteByte()
methods:
byte[] bytes = new byte[100];
int bytesRead = stream.Read(bytes, 0, 100);
stream.Write(bytes, 0, bytesRead);
This reads binary data from a stream into a byte buffer and writes it back out again.
Random File Access with Seek()
By default, file streams open at the beginning of a file. But you can use Seek()
to move the stream position, allowing random access at any location:
stream.Seek(50, SeekOrigin.Begin); // Seek to byte 50
byte b = stream.ReadByte(); // Read single byte from offset 50
SeekOrigin
specifies whether the offset should be from the beginning, current position, or end of file.
Getting File Info
You can retrieve metadata on a file like its size using static File
class methods:
long size = new FileInfo("data.bin").Length;
DateTime time = File.GetLastWriteTime("data.bin");
Useful for getting file stats before reading or determining file availability.
Reading Large Files in Chunks
When accessing very large files, you generally want to avoid reading the entire contents into memory at once. Here is an example using a buffer and loop for reading in chunks:
const int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesRead;
using (FileStream stream = File.Open("massive-file.data"))
{
do
{
bytesRead = stream.Read(buffer, 0, bufferSize);
// Process buffer contents
} while (bytesRead > 0);
}
This prevents out-of-memory crashes by only allocating a small fixed buffer. The data can then be processed in chunks as it is read.
File and Directory Operations
The static File
and Directory
classes provide a variety of utility methods for creating, deleting, moving, and querying files and directories.
For example, to copy a file:
File.Copy("source.txt", "destination.txt");
Or create a directory if it doesn‘t exist:
if (!Directory.Exists("logs"))
{
Directory.CreateDirectory("logs");
}
You can also delete files, retrieve file lists in a directory, combine paths, get disk space info, and much more.
Asynchronous File I/O
All the file classes discussed also provide asynchronous versions of their methods to avoid blocking the calling thread:
await File.WriteAllBytesAsync(filePath, bytes);
Task<string> contentsTask = File.ReadAllTextAsync(filePath);
These asynchronous operations return Task
objects. The await
keyword (usable in async methods) pauses execution until the file task completes.
File and Directory Permissions
File system permissions can restrict or grant access when trying to open files from certain accounts or processes.
By default C# inherits the permissions of the account it is running under. But in some cases you may need elevated admin permissions or impersonation to access secured resources:
FileStream file = new FileStream(path, mode, access, share, bufferSize, options);
file.GetAccessControl();
File.GetAccessControl(path);
File.SetAccessControl(path, security);
The key options and methods related to permissions are shown above. This allows getting or setting access control rules programmatically.
Conclusion
In this detailed guide we looked at many examples of opening, reading, writing, copying, appending to, and querying files in C# using classes like FileStream
, StreamReader
, StreamWriter
and others.
Key concepts included:
- Using
FileStream
for low-level byte/stream access - Leveraging
StreamReader
andStreamWriter
for convenient text file handling - Appending data safely to existing files
- Efficiently processing large files in chunks
- Getting file metadata and manipulating the file system
- Support for synchronous and asynchronous operations
- Dealing with permissions restrictions
Together these I/O classes provide powerful and flexible file manipulation capabilities for C# applications. They are suitable for everyday text, binary, random access, and large dataset scenarios.