Introduction to Streams

A stream is an object that essentially connects data between two endpoints with a narrow access window. Streams allow you to write and read small portions of data at a time, providing for an extremely efficient means of access. For example, using a stream, you can read data in small portions from a file that is several hundred megabytes in size without actually consuming several hundred megabytes of memory. The same is true of writing to streams. You can place small amounts of data on a stream without having to have all of the data in memory at any given time.

In addition to the performance benefit, streams also provide a unified model for reading and writing data, regardless of the format or location of the underlying data. For example, you can access data from a disk file using a stream and you can access data from a relational database or from a web service in a stream as well.

Streams can also be connected through a process referred to as composition. By composing streams, you can attach various types of reader and writer classes to the end of the stream to make data access easier. As you will see in Chapter 15 "Cryptography and Data Protection," you can even attach specially encrypted streams so that data is encrypted as soon as it is placed on the stream. Streams can also be used for network communication in addition to file I/O.

Using Memory Streams

Streams are the basic unit of I/O in the .NET Framework and you will find them used everywhere. Before getting into working with physical files on disk, this section will illustrate the basics of opening streams and reading and writing from streams using the MemoryStream class as an example. When you know how to manipulate a MemoryStream, you will find that you will be able to use all of the other types of streams exposed by .NET Framework classes with little difficulty.

As mentioned earlier, streams provide a narrow window of access. This often causes developers trouble. For example, when you write to a stream, the Position of the pointer in the stream advances. When you read from a stream, the read always starts from the current pointer position within the stream. A common source of problems when reading from streams is not setting the pointer position properly with the Seek method.

Before taking a look at the MemoryStream sample code, take a look at Tables 7.1 and 7.2, which list the methods and properties that belong to all Stream classes, regardless of the underlying data store.

Table 7.1. Stream Properties




This property is used by deriving classes (such as MemoryStream) to indicate whether the stream supports read operations.


Indicates whether the stream supports write operations.


Indicates whether the stream supports seek operations. Some streams are forward-only and do not allow seeking to specific positions.


Indicates the length, or size, of the stream in bytes.


Indicates the current pointer position of the stream.

Table 7.2. Stream Methods




Starts an asynchronous (multithreaded) read operation


Starts an asynchronous write operation


Closes the current stream and releases associated resources (such as underlying database resources, network sockets, OS-level file handles, and so on)


Completes an asynchronous read operation


Completes an asynchronous write operation


Clears any buffers in the stream and stores any uncommitted data in the underlying backing store


Reads an array of bytes from the stream


Reads a single byte from the stream


Moves the pointer to the indicated position, relative to the beginning, end, or current position of the stream


Expands or contracts the stream, if supported


Writes an array of bytes to the stream


Writes a single byte to the stream

The code shown in Listing 7.1 illustrates how to instantiate a stream, as well as how to read and write information from that stream.

Listing 7.1. MemoryStream Sample

using System; using System.IO; using System.Collections.Generic; using System.Text; namespace MemStream {     class Program     {         static void Main(string[] args)         {             string sourceString = "Mary had a little lamb.";             MemoryStream ms = new MemoryStream(100) ;             // put some data on the stream             ms.Write(ASCIIEncoding.ASCII.GetBytes(sourceString),               0, sourceString.Length);             Console.WriteLine("After initial write:");             Console.WriteLine(string.Format(                 "Capacity: {0}\nLength: {1}\nPosition: {2}",                 ms.Capacity, ms.Length, ms.Position));             ms.Seek(0, SeekOrigin.Begin);             // read the first 4 bytes of the stream             byte[] tempBytes = new byte[sourceString.Length];             ms.Read(tempBytes, 0, 4);             Console.WriteLine(ASCIIEncoding.ASCII.GetString(tempBytes));             // get the word 'lamb'             ms.Seek(-5, SeekOrigin.End);             ms.Read(tempBytes, 0, 4) ;             Console.WriteLine(ASCIIEncoding.ASCII.GetString(tempBytes));             // write some bytes             ms.Seek(11, SeekOrigin.Begin);             ms.Write(ASCIIEncoding.ASCII.GetBytes("really"),0, 6);             // now get the whole stream             ms.Seek(0, SeekOrigin.Begin);             ms.Read(tempBytes, 0, (int)ms.Length);             Console.WriteLine(ASCIIEncoding.ASCII.GetString(tempBytes));             ms.Close();             Console.ReadLine();         }     } } 

Before running this code, see if you can predict the output. The code that retrieves the first four bytes of the stream should be fairly obvious; the output will be the word Mary.

Working with Unicode

When working with streams that contain textual data, knowing the size of your characters is paramount. For example, when working with traditional ASCII characters, the size of each character is exactly one byte. This makes math easy. However, when working with Unicode when dealing with foreign languages, you need to remember that each character requires two bytes. Therefore, when you convert byte arrays into strings, you need to use the UnicodeEncoding class, and you need to allocate twice the number of characters in bytes when initializing your byte arrays.

Next, the code seeks to a position five bytes before the end of the stream and then grabs the next four bytes, producing the word "lamb."

The next few lines of code are typically where a lot of developers get confused. When you write to a stream, you overwrite whatever bytes might lie underneath. So, when writing "really" to the stream, you don't get "Mary had a really little lamb." as one might expect. Rather, your stream contains "Mary had a really lamb.". If your goal is truly to insert data at a certain point in the stream, and have the remainder of the stream remain intact, you will need to do it the "old-fashioned way." This involves using two streams and copying the old into the new, making sure to insert the new data in the right place in the new stream.

Microsoft Visual C# 2005 Unleashed
Microsoft Visual C# 2005 Unleashed
ISBN: 0672327767
EAN: 2147483647
Year: 2004
Pages: 298 © 2008-2017.
If you may any questions please contact us: