Handling Data I/O in C# Using Streams
In C#, input and output operations are primarily handled through the System.IO namespace. The central concept is the Stream, an abstract class that provides a generic view of a sequence of bytes. Streams involve three fundamental operations: reading, writing, and seeking.
The System.IO namespace organizes IO functionality into several key classes:
- Stream: The abstract base class. Key derived classes include:
- MemoryStream: Operates on data stored in memory.
- BufferedStream: Adds a buffering layer to read/write operations on another stream to improve performance.
- FileStream: Designed for file-based operations.
- BinaryReader/BinaryWriter: Helper classes for reading and writing primitive data types as binary values.
- TextReader/TextWriter: Abstract base classes for handling character-based I/O. Their derived classes include StreamReader/StreamWriter (for files) and StringReader/StringWriter (for strings).
Memory and Buffered Streams
The following example demonstrates how to use a MemoryStream backed by a BufferedStream. Data is written to the buffer and then read back.
using System;
using System.IO;
public class MemoryBufferExample
{
public void Execute()
{
// Create a memory stream wrapped in a buffered stream
using (var memoryStream = new MemoryStream())
using (var bufferedStream = new BufferedStream(memoryStream))
{
// Write bytes 0 through 9
for (int i = 0; i < 10; i++)
{
bufferedStream.WriteByte((byte)i);
}
// Reset the position to the beginning to read data
bufferedStream.Position = 0;
// Read data into a byte array
byte[] buffer = new byte[10];
bufferedStream.Read(buffer, 0, buffer.Length);
foreach (byte b in buffer)
{
Console.WriteLine($"Value read: {b}");
}
// Read a single byte manually (will be the last one read or -1 if end of stream)
int nextByte = bufferedStream.ReadByte();
Console.WriteLine($"Final byte read attempt: {nextByte}");
}
}
}
File Streams with Binary Readers and Writers
For binary data, FileStream is often paired with BinaryWriter and BinaryReader. It is crucial to read data using the correct data type; for instance, an int written via BinaryWriter occupies 4 bytes and must be read using ReadInt32, not ReadByte.
using System;
using System.IO;
public class FileBinaryExample
{
public void ProcessFile(string path)
{
// Write binary data to a file
using (var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
using (var writer = new BinaryWriter(fileStream))
{
for (int i = 1; i <= 10; i++)
{
writer.Write(i); // Writes a 4-byte integer
}
}
// Read binary data from the file
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
using (var reader = new BinaryReader(fileStream))
{
// Ensure the stream is read to the end
while (fileStream.Position < fileStream.Length)
{
// Must use ReadInt32 to match the written data type
int value = reader.ReadInt32();
Console.WriteLine($"Integer read: {value}");
}
}
}
}
Text Streams
When dealing with text files, StreamWriter and StreamReader simplify the process of writing and reading strings. These classes handle character encoding automatically.
using System;
using System.IO;
public class TextFileExample
{
public void ManageTextFile(string filePath)
{
// Write text to the file
// The 'true' argument appends data; 'false' overwrites the file
using (var writer = new StreamWriter(filePath, false))
{
for (int i = 1; i < 5; i++)
{
writer.WriteLine($"Line number: {i}");
}
}
// Read text from the file
using (var reader = new StreamReader(filePath))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
}
String Streams
StringWriter and StringReader allow you to treat strings as streams. This is useful for manipulating string data in memory using the stream interface. StringWriter typically wraps a StringBuilder.
using System;
using System.IO;
using System.Text;
public class StringStreamExample
{
public void ProcessStringData()
{
var stringBuilder = new StringBuilder();
// Write to the StringBuilder via StringWriter
using (var stringWriter = new StringWriter(stringBuilder))
{
for (int i = 0; i < 5; i++)
{
stringWriter.WriteLine($"Input entry {i}");
}
}
string content = stringBuilder.ToString();
Console.WriteLine("Full Content:");
Console.WriteLine(content);
// Read from the string via StringReader
using (var stringReader = new StringReader(content))
{
string line = stringReader.ReadLine();
Console.WriteLine($"First line read: {line}");
}
}
}