What’s in This Chapter
Stream
, FileStream
, and MemoryStream
classesWrox.com Downloads for This Chapter
Please note that all the code examples for this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com/go/csharp5programmersref on the Download Code tab.
At a basic level, all pieces of data are just collections of bytes. The computer doesn’t actually store invoices, employee records, and recipes. At its most basic level, the computer stores bytes of information. (Or even bits, but the computer naturally groups them in bytes.) It is only when a program interprets those bytes that they acquire a higher-level meaning that is valuable to the user.
Usually it’s not helpful to treat high-level data as undifferentiated bytes, but there are times when it’s useful to ignore the higher-level structure of the data and treat it as just a bunch of bytes.
One important way of thinking about data is the stream, an ordered series of bytes. Files, data flowing across a network, messages moving through a queue, and even the memory in an array can all fit this description.
Defining the abstract idea of a stream lets applications handle these different types of objects uniformly. For example, a cryptographic algorithm can process the bytes in a stream without knowing whether they represent employees, prescription information, or an image.
Visual Studio provides several classes for manipulating different kinds of streams. It also provides higher-level classes for working with streams that represent specific kinds of data. For example, it includes classes for working with streams that represent text files.
This chapter describes some of the classes you can use to manipulate streams. It explains lower-level classes that you may use only rarely and higher-level classes that let you read and write strings and files relatively easily.
The following table summarizes the most useful stream classes.
Class | Use |
Stream | The parent class of other stream classes. Tools that manipulate streams in the most general way work with Stream objects. |
FileStream | Read and write bytes in a file. |
MemoryStream | Read and write bytes in memory. |
BinaryReader , BinaryWriter | Read and write specific data types in a stream. |
StringReader , StringWriter | Read and write text with or without new lines in a string. |
StreamReader , StreamWriter | Read and write text with or without new lines in a stream (usually a file stream). |
The following sections describe some of these classes in greater detail.
The Stream
class defines properties and methods that derived stream classes must provide. These let the program perform relatively generic tasks with streams such as determining whether the stream allows writing and deciding when the stream has reached its end.
The following table describes the Stream
class’s most useful properties.
Property | Purpose |
CanRead | True if the stream supports reading. |
CanSeek | True if the stream supports seeking to a particular position in the stream. |
CanTimeout | True if the stream supports timing out of read and write operations. |
CanWrite | True if the stream supports writing. |
Length | The number of bytes in the stream. |
Position | The stream’s current position. For a stream that supports seeking, the program can set this value to move to a particular position. |
ReadTimeout | The number of milliseconds that a read operation waits before timing out. |
WriteTimeout | The number of milliseconds that a write operation waits before timing out. |
The following table describes the Stream
class’s most useful methods.
Method | Purpose |
BeginRead | Starts an asynchronous read. |
BeginWrite | Starts an asynchronous write. |
Close | Closes the stream and releases its resources. |
Dispose | Releases the stream’s resources. |
EndRead | Waits for an asynchronous read to finish. |
EndWrite | Ends an asynchronous write. |
Flush | Flushes data from the stream’s buffers into the underlying storage such as a file or piece of memory. |
Read | Reads bytes from the stream and advances its position by that number of bytes. |
ReadByte | Reads a byte from the stream and advances its position by one byte. |
Seek | If the stream supports seeking, sets the stream’s position. |
SetLength | Sets the stream’s length. If the stream is currently longer than the new length, it is truncated. If the stream is shorter than the new length, it is enlarged. The stream must support both writing and seeking for this method to work. |
Write | Writes bytes into the stream and advances the current position by this number of bytes. |
WriteByte | Writes 1 byte into the stream and advances the current position by 1 byte. |
For more information about the Stream
class, see msdn.microsoft.com/system.io.stream.aspx.
The FileStream
class represents a stream associated with a file. Its parent class Stream
defines most of its properties and methods. See the preceding section for descriptions of those properties and methods.
FileStream
adds two useful new properties to those it inherits from Stream
. First, IsAsync
returns true
if the FileStream
was opened asynchronously. Second, the Name
property returns the name of the file passed into the object’s constructor.
The class also adds two new, useful methods to those it inherits from Stream
. The Lock
method locks the file, so other processes can read it but not modify it. Unlock
removes a previous lock.
Overloaded versions of the FileStream
class’s constructor let you specify the following.
Append
, Create
, CreateNew
, Open
, OpenOrCreate
, or Truncate
)Read
, Write
, or ReadWrite
)Inheritable
, which allows child processes to inherit the file handle; None
; Read
; Write
; or ReadWrite
)Asynchronous
, DeleteOnClose
, Encrypted
, None
, RandomAccess
, SequentialScan
, or WriteThrough
)Example program WriteIntoFileStream, which is available for download on this book’s website, uses the following code to create and write into a text file.
string filename = filenameTextBox.Text;
using (FileStream filestream = new FileStream(filename, FileMode.Create))
{
byte[] bytes = new UTF8Encoding().GetBytes(textTextBox.Text);
filestream.Write(bytes, 0, bytes.Length);
}
This code gets the file’s name from the filenameTextBox
. It passes the name and the file access parameter Create
to the FileStream
constructor.
The UTF8Encoding
object represents UTF-8 encoded characters. The code creates such an object and uses its GetBytes
method to create a byte array representing the text in textTextBox
.
The code then writes the bytes into the file stream. The using
statement ensures that the stream is disposed, and that flushes and closes the stream.
As this example demonstrates, the FileStream
class provides only low-level methods for reading and writing files. These methods let you read and write bytes, but not integers, strings, or the other types of data that you are more likely to want to use.
The BinaryReader
and BinaryWriter
classes make it easier to work with binary data. Similarly, the StringReader
and StringWriter
classes make it easier to work with strings. See the sections “BinaryReader
and BinaryWriter
” and “StringReader
and StringWriter
” later in this chapter for more information on those classes.
The MemoryStream
class represents a stream with data stored in memory. Like the FileStream
class, it provides relatively primitive methods for reading and writing data. Usually, you’ll want to attach a higher-level object to the MemoryStream
to make it easier to use.
Example program WriteIntoMemoryStream, which is available for download on this book’s website, uses the following code to write and read from a MemoryStream
object.
// Create the stream.
MemoryStream stream = new MemoryStream();
// Write into the stream.
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(textTextBox.Text);
// Read from the stream.
stream.Seek(0, SeekOrigin.Begin);
BinaryReader reader = new BinaryReader(stream);
MessageBox.Show(reader.ReadString());
// Clean up.
writer.Dispose();
reader.Dispose();
stream.Dispose();
The code first creates a MemoryStream
. It then creates a BinaryWriter
associated with the stream and uses its Write
method to write a string into it.
Next, the code uses the Seek
method to rewind the stream to the beginning of the data. It then creates a BinaryReader
associated with the stream, uses its ReadString
method to read a string from the stream, and displays the string in a message box.
The code finishes by disposing of the objects it used. This is a bit more confusing than usual because the reader and writer are associated with the stream. When the program disposes of the reader or writer, those objects automatically close their underlying stream. That means you cannot dispose of the writer before you finish with the reader. If you are careful, you can use a properly ordered sequence of using
statements, but this example seems simpler if you just dispose of the objects all at once at the end.
The BinaryReader
and BinaryWriter
classes are helper classes that work with stream classes. They provide an interface that makes it easier to read and write data in a stream. For example, the BinaryReader
class’s ReadInt32
method reads a 4-byte (32-bit) signed integer from the stream. Similarly, the ReadUInt16
method reads a 2-byte (16-bit) unsigned integer.
These classes still work at a relatively low level, and you should generally use higher-level classes to read and write data if possible. For example, you shouldn’t tie yourself to a particular representation of an integer (32- or 16-bit) unless you must.
Both the BinaryReader
and BinaryWriter
classes have a BaseStream
property that returns a reference to the underlying stream. Note that their Close
and Dispose
methods automatically close their underlying streams.
The following table describes the BinaryReader
class’s most useful methods.
Method | Purpose |
Close | Closes the BinaryReader and its underlying stream. |
PeekChar | Reads the stream’s next character but does not advance the reader’s position. |
Read | Reads characters from the stream and advances the reader’s position. |
ReadBoolean | Reads a bool from the stream and advances the reader’s position by 1 byte. |
ReadByte | Reads a byte from the stream and advances the reader’s position by 1 byte. |
ReadBytes | Reads a specified number of byte s from the stream into a byte array and advances the reader’s position by that number of bytes. |
ReadChar | Reads a char from the stream and advances the reader’s position appropriately for the stream’s encoding. |
ReadChars | Reads a specified number of char s from the stream, returns the results in a char array, and advances the reader’s position appropriately for the stream’s encoding. |
ReadDecimal | Reads a decimal value from the stream and advances the reader’s position by 16 bytes. |
ReadDouble | Reads an 8-byte double from the stream and advances the reader’s position by 8 bytes. |
ReadInt16 | Reads a 2-byte short from the stream and advances the reader’s position by 2 bytes. |
ReadInt32 | Reads a 4-byte int from the stream and advances the reader’s position by 4 bytes. |
ReadInt64 | Reads an 8-byte long from the stream and advances the reader’s position by 8 bytes. |
ReadSByte | Reads a signed sbyte from the stream and advances the reader’s position by 1 byte. |
ReadSingle | Reads a 4-byte float from the stream and advances the reader’s position by 4 bytes. |
ReadString | Reads a string from the current stream and advances the reader’s position past it. |
ReadUInt16 | Reads a 2-byte unsigned ushort from the stream and advances the reader’s position by 2 bytes. |
ReadUInt32 | Reads a 4-byte unsigned uint from the stream and advances the reader’s position by 4 bytes. |
ReadUInt64 | Reads an 8-byte unsigned ulong from the stream and advances the reader’s position by 8 bytes. |
The following table describes the BinaryWriter
class’s most useful methods.
Method | Purpose |
Close | Closes the BinaryWriter and its underlying stream. |
Flush | Writes any buffered data into the underlying stream. |
Seek | Sets the position within the stream. |
Write | Writes a value into the stream. This method has many overloaded versions to write char , char[] , int , string , ulong , and other data types into the stream. |
For more information about these classes, see msdn.microsoft.com/system.io.binarywriter.aspx and msdn.microsoft.com/system.io.binaryreader.aspx.
Like the BinaryReader
and BinaryWriter
classes, the TextReader
and TextWriter
classes provide an interface for an underlying stream. As you can probably guess from their names, these classes provide methods for working with text.
TextReader
and TextWriter
are abstract classes, so you cannot create instances of them. They define behaviors for the derived classes that you can instantiate.
For example, the StringWriter
and StreamWriter
classes derived from TextWriter
let a program write characters into a string or stream, respectively.
Normally, you would use these derived classes to read and write text, but you might want to use the TextReader
or TextWriter
classes to manipulate the underlying classes more generically. You may also find .NET Framework methods that require a TextReader
or TextWriter
object as a parameter. In that case, you could pass the method either a StringReader
/StringWriter
or a StreamReader
/StreamWriter
. (For more information on these classes, see the sections “StringReader and StringWriter” and “StreamReader and StreamWriter” later in this chapter.)
The following table describes the TextReader
class’s most useful methods.
Method | Purpose |
Close | Closes the reader and releases its resources. |
Peek | Reads the next character from the input without changing the reader’s state, so other methods can read the character later. |
Read | Reads data from the input. Overloaded versions of this method read a single char or an array of char up to a specified length. |
ReadBlock | Reads data from the input into an array of char . |
ReadLine | Reads a line of characters from the input and returns the data in a string . |
ReadToEnd | Reads any remaining characters in the input and returns them in a string . |
The TextWriter
class has three useful properties. Encoding
specifies the text’s encoding (ASCII, UTF-8, Unicode, and so forth).
The FormatProvider
property returns an object that controls formatting. For example, you can build a FormatProvider
object that knows how to display numbers in different bases (such as hexadecimal or octal).
The NewLine
property gets or sets the string used by the writer to end lines. Usually, this value is something similar to a carriage return or a carriage return plus a line feed.
The following table describes the TextWriter
class’s most useful methods.
Method | Purpose |
Close | Closes the writer and releases its resources. |
Flush | Writes any buffered data into the underlying stream. |
Write | Writes a value into the stream. This method has many overloaded versions that write char , char[] , int , string , ulong , and other data types. |
WriteLine | Writes data into the output followed by the new-line sequence. |
For more information about the TextWriter
and TextReader
classes, see msdn.microsoft.com/system.io.textwriter.aspx and msdn.microsoft.com/system.io.textreader.aspx.
The StringReader
and StringWriter
classes let a program read and write text in a string.
These classes are derived from TextReader
and TextWriter
, so they inherit most of their properties and methods from those classes. See the preceding section for details.
The StringReader
class provides methods for reading lines, characters, or blocks of characters from a string
. The StringReader
class’s constructor takes as a parameter the string that it should process. Its ReadToEnd
method returns the part of the string that has not already been read.
The StringWriter
class lets an application build a string. It provides methods to write text into the string with or without a new-line sequence afterward. Its ToString
method returns the string represented by the object.
The StringWriter
stores its string in an underlying StringBuilder
object. The StringBuilder
class is designed to make incrementally building a string more efficient than building a string by concatenating a series of values onto a string
variable. For example, if an application needs to build a large string by concatenating a series of long substrings, it may be more efficient to use a StringBuilder
rather than add the strings to a normal string
variable by using the +
operator. StringWriter
provides a simple interface to the StringBuilder
class.
The most useful method provided by StringWriter
that is not defined by the TextWriter
parent class is GetStringBuilder
. This method returns a reference to the underlying StringBuilder
object that holds the object’s data.
Example program StringWriterAndReader, which is available for download on this book’s website, uses the following code to demonstrate the StringWriter
and StringReader
classes.
// Use a StringWriter to write into a string.
using (StringWriter writer = new StringWriter())
{
// Write the strings entered by the user.
writer.WriteLine(textBox1.Text);
writer.WriteLine(textBox2.Text);
writer.WriteLine(textBox3.Text);
// Display the result.
string result = writer.ToString();
MessageBox.Show(result);
// Read the result with a StringReader.
using (StringReader reader = new StringReader(result))
{
// Read one line.
MessageBox.Show(reader.ReadLine());
// Read the rest.
MessageBox.Show(reader.ReadToEnd());
}
}
The code starts by creating a StringWriter
and using its WriteLine
method three times to add the text entered by the user in TextBox
es to the string.
The code then saves the StringWriter
’s underlying string into the variable result
and displays it in a message box.
Next, the code creates a StringReader
associated with the result string. It uses the reader’s ReadLine
method to read one line from the string and displays it. The program finishes by using the ReadToEnd
method to read and display the rest of the string.
The StreamReader
and StreamWriter
classes let a program read and write data in a stream, usually a FileStream
. You can pass a FileStream
into these classes’ constructors, or you can pass a filename and the object creates a FileStream
automatically.
The StreamReader
class provides methods for reading lines, characters, or blocks of characters from the stream. Its ReadToEnd
method returns any parts of the stream that have not already been read. The EndOfStream
property is true
when the StreamReader
has reached the end of its stream.
Example program ReadLines, which is available for download on this book’s website, uses the following code fragment to read the lines from a file and add them to a ListBox
control.
using (StreamReader reader = new StreamReader("Animals.txt"))
{
// Read until we reach the end of the file.
do
{
animalListBox.Items.Add(reader.ReadLine());
}
while (!reader.EndOfStream);
}
The StreamWriter
class provides methods to write text into the stream with or without a new-line character.
StreamReader
and StreamWriter
are derived from the TextReader
and TextWriter
classes and inherit most of their properties and methods from those classes. See the section “TextReader
and TextWriter
” earlier in this chapter for a description of those properties and methods.
The StreamWriter
class adds a new AutoFlush
property that determines whether the writer flushes its buffer after every write. This is useful if the program periodically writes to the same file and you want to make sure the contents are flushed. For example, a program could write into a log file every few minutes. If you set AutoFlush
to true
, then the output is always written into the file, so you can use Notepad or some other program to look at the file and see the latest entries.
Example program WriteLog, which is available for download on this book’s website, uses the following code to demonstrate the StreamWriter
class’s AutoFlush
property.
// The log file stream.
private StreamWriter Writer;
// Open the log file.
private void Form1_Load(object sender, EventArgs e)
{
Writer = new StreamWriter("Log.txt", true);
Writer.AutoFlush = true;
}
// Write an entry into the log.
private void writeButton_Click(object sender, EventArgs e)
{
Writer.WriteLine(DateTime.Now.ToString() + ": " + entryTextBox.Text);
entryTextBox.Clear();
entryTextBox.Focus();
}
// Close the log file.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Writer.Dispose();
}
The program’s Form_Load
event handler opens the log file and sets the StreamWriter
’s AutoFlush
property to true
.
When you click its Write button, the program adds the current time and the text you entered in the TextBox
to the log file.
The program’s FormClosing
event handler disposes of the StreamWriter
.
While the program is running, use Notepad to view the log file and see the most recent entries. Comment out the code that sets AutoFlush
to true
and run the program again to see what happens.
The System.IO.File
class provides four shared methods that are particularly useful for working with StreamReader
and StreamWriter
objects associated with text files. The following table summarizes these four methods.
Method | Purpose |
Exists | Returns true if a file with a given path exists |
OpenText | Returns a StreamReader that reads from an existing text file |
CreateText | Creates a new text file, overwriting the file if it exists, and returns a StreamWriter that lets you write into the new file |
AppendText | Opens or creates the file and returns a StreamWriter that lets you append text at the end of the file |
The .NET Framework also provides a few other stream classes with more specialized uses.
The CryptoStream
class applies a cryptographic transformation to the data passing through it. For example, if you attach a CryptoStream
to a file, the CryptoStream
can automatically encrypt or decrypt the data as it reads or writes to the file. (Chapter 27, “Cryptography,” has more to say about cryptography.)
The NetworkStream
class represents a socket-based stream over a network connection. You can use this class to make different applications communicate over a network. For more information about this class, see msdn.microsoft.com/library/system.net.sockets.networkstream.aspx.
Three special streams represent a program’s standard input, standard output, and standard error. Console applications define these streams for reading and writing information to and from the console. Applications can also interact directly with these streams by accessing the Console
class’s In
, Out
, and Error
properties. A program can change those streams to new stream objects such as StreamReader
s and StreamWriter
s by calling the Console
class’s SetIn
, SetOut
, and SetError
methods. For example, a program could redirect the error stream into a file. For more information on these streams, see msdn.microsoft.com/library/system.console.aspx.
Streams let a program treat a wide variety of data sources in a uniform way. That’s useful for generalizable methods such as cryptographic algorithms or data compression routines, but in practice you often want to use specialized classes that make working with particular kinds of data easier.
For example, the StringReader
and StringWriter
classes read and write text in strings, and the StreamReader
and StreamWriter
classes read and write text in streams (usually files). The File
class’s Exists
, OpenText
, CreateText
, and AppendText
methods are particularly useful for working with StreamReader
and StreamWriter
objects associated with text files.
Stream classes let a program interact with files. The next chapter explains other classes that you can use to interact with the filesystem. These classes let a program examine, rename, move, and delete files and directories.
Dispose
statements to free its MemoryStream
, BinaryWriter
, and BinaryReader
objects. Rewrite the code with using
statements instead. Which version is easier to read?File
class methods and streams to save and restore some text when it starts and stops. When it starts, the program should open a text file (if it exists) and display its contents in a multiline TextBox
. When it is closing, the program should save the TextBox
’s contents into the file, overwriting its previous contents.StreamWriter
’s Write
or WriteLine
method. Which of those methods should you use and why?ListBox
.ListBox
. (Hint: The BinaryReader
class doesn’t have an EndOfStream
property. To let it know how many values to read, save the number of primes at the beginning of the file.)