Chapter 18
Streams

What’s in This Chapter

  • Stream, FileStream, and MemoryStream classes
  • Stream readers and writers
  • Opening text files
  • Special stream classes

Wrox.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.

ClassUse
StreamThe parent class of other stream classes. Tools that manipulate streams in the most general way work with Stream objects.
FileStreamRead and write bytes in a file.
MemoryStreamRead and write bytes in memory.
BinaryReader, BinaryWriterRead and write specific data types in a stream.
StringReader, StringWriterRead and write text with or without new lines in a string.
StreamReader, StreamWriterRead 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.

Stream

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.

PropertyPurpose
CanReadTrue if the stream supports reading.
CanSeekTrue if the stream supports seeking to a particular position in the stream.
CanTimeoutTrue if the stream supports timing out of read and write operations.
CanWriteTrue if the stream supports writing.
LengthThe number of bytes in the stream.
PositionThe stream’s current position. For a stream that supports seeking, the program can set this value to move to a particular position.
ReadTimeoutThe number of milliseconds that a read operation waits before timing out.
WriteTimeoutThe number of milliseconds that a write operation waits before timing out.

The following table describes the Stream class’s most useful methods.

MethodPurpose
BeginReadStarts an asynchronous read.
BeginWriteStarts an asynchronous write.
CloseCloses the stream and releases its resources.
DisposeReleases the stream’s resources.
EndReadWaits for an asynchronous read to finish.
EndWriteEnds an asynchronous write.
FlushFlushes data from the stream’s buffers into the underlying storage such as a file or piece of memory.
ReadReads bytes from the stream and advances its position by that number of bytes.
ReadByteReads a byte from the stream and advances its position by one byte.
SeekIf the stream supports seeking, sets the stream’s position.
SetLengthSets 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.
WriteWrites bytes into the stream and advances the current position by this number of bytes.
WriteByteWrites 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.

FileStream

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.

  • A filename or file handle
  • File mode (Append, Create, CreateNew, Open, OpenOrCreate, or Truncate)
  • Access mode (Read, Write, or ReadWrite)
  • File sharing (Inheritable, which allows child processes to inherit the file handle; None; Read; Write; or ReadWrite)
  • Buffer size
  • File options (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.

MemoryStream

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.

BinaryReader and BinaryWriter

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.

MethodPurpose
CloseCloses the BinaryReader and its underlying stream.
PeekCharReads the stream’s next character but does not advance the reader’s position.
ReadReads characters from the stream and advances the reader’s position.
ReadBooleanReads a bool from the stream and advances the reader’s position by 1 byte.
ReadByteReads a byte from the stream and advances the reader’s position by 1 byte.
ReadBytesReads a specified number of bytes from the stream into a byte array and advances the reader’s position by that number of bytes.
ReadCharReads a char from the stream and advances the reader’s position appropriately for the stream’s encoding.
ReadCharsReads a specified number of chars from the stream, returns the results in a char array, and advances the reader’s position appropriately for the stream’s encoding.
ReadDecimalReads a decimal value from the stream and advances the reader’s position by 16 bytes.
ReadDoubleReads an 8-byte double from the stream and advances the reader’s position by 8 bytes.
ReadInt16Reads a 2-byte short from the stream and advances the reader’s position by 2 bytes.
ReadInt32Reads a 4-byte int from the stream and advances the reader’s position by 4 bytes.
ReadInt64Reads an 8-byte long from the stream and advances the reader’s position by 8 bytes.
ReadSByteReads a signed sbyte from the stream and advances the reader’s position by 1 byte.
ReadSingleReads a 4-byte float from the stream and advances the reader’s position by 4 bytes.
ReadStringReads a string from the current stream and advances the reader’s position past it.
ReadUInt16Reads a 2-byte unsigned ushort from the stream and advances the reader’s position by 2 bytes.
ReadUInt32Reads a 4-byte unsigned uint from the stream and advances the reader’s position by 4 bytes.
ReadUInt64Reads 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.

MethodPurpose
CloseCloses the BinaryWriter and its underlying stream.
FlushWrites any buffered data into the underlying stream.
SeekSets the position within the stream.
WriteWrites 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.

TextReader and TextWriter

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.

MethodPurpose
CloseCloses the reader and releases its resources.
PeekReads the next character from the input without changing the reader’s state, so other methods can read the character later.
ReadReads data from the input. Overloaded versions of this method read a single char or an array of char up to a specified length.
ReadBlockReads data from the input into an array of char.
ReadLineReads a line of characters from the input and returns the data in a string.
ReadToEndReads 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.

MethodPurpose
CloseCloses the writer and releases its resources.
FlushWrites any buffered data into the underlying stream.
WriteWrites a value into the stream. This method has many overloaded versions that write char, char[], int, string, ulong, and other data types.
WriteLineWrites 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.

StringReader and StringWriter

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 TextBoxes 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.

StreamReader and StreamWriter

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.

Exists, OpenText, CreateText, and AppendText

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.

MethodPurpose
ExistsReturns true if a file with a given path exists
OpenTextReturns a StreamReader that reads from an existing text file
CreateTextCreates a new text file, overwriting the file if it exists, and returns a StreamWriter that lets you write into the new file
AppendTextOpens or creates the file and returns a StreamWriter that lets you append text at the end of the file

Custom Stream Classes

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 StreamReaders and StreamWriters 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.

Summary

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.

Exercises

  1. The WriteIntoMemoryStream example program uses Dispose statements to free its MemoryStream, BinaryWriter, and BinaryReader objects. Rewrite the code with using statements instead. Which version is easier to read?
  2. What happens if you don’t dispose of a stream attached to a file? A memory stream? Which case is worse?
  3. Write a program that reads and uses 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.
  4. One way a solution to Exercise 3 can save text is by using a StreamWriter’s Write or WriteLine method. Which of those methods should you use and why?
  5. Modify the program you wrote for Exercise 3 to prompt the user to ask if it should overwrite the file. Take appropriate action when the user clicks Yes, No, or Cancel.
  6. Write a program that indicates whether the lines in a file are in alphabetical order. Assume the file is huge, so the program must read the file one line at a time and compare each line to the previous one. (That way it needs to store only two lines of text at any given time. It can also stop if it ever finds two lines out of order.)
  7. Modify the program you wrote for Exercise 14-8 so that it writes the primes into the file Primes.txt in addition to displaying them in a ListBox.
  8. Modify the program you wrote for Exercise 7 so that it saves the primes in a binary file named Primes.dat. When the program starts, it should read that file (if it exists) and display the saved values in the 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.)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.131.152.166