< Chapter 2 TOC | Main | FileStream Examples >

Chapter 2 Part 1:

Managed I/O - Streams, Readers, and Writers



What do we have in this chapter 2 Part 1?

  1. An Overview

  2. Stream Types

  3. Stream Class

  4. Basic Operations

  5. Common Properties

  6. Synchronous and Asynchronous I/O

  7. Base Streams

  8. File Stream

  9. Creating or Opening a File



Note: If you want to experience a complete C++ .NET/C++-CLI programming tutorial please jump to Visual C++ .NET programming tutorial.


An Overview





The Microsoft Windows .NET Framework offers a unified way to access operating system resources such as files, memory, and network resources through the stream I/O pattern. Streams provide a common way to perform I/O operations regardless of the operating system resource. In general, streams allow you to read and write data sequentially to a container (which we call a backing store) as if the data is a continuous sequence of bytes where the first byte that’s written becomes the first byte to be read, and so on. The container is called a backing store because it will typically hold or store the data that’s written to the stream. Figure 2-1 describes a stream as a sheet of paper where the paper represents a backing store that can hold letters of the alphabet. A supplier or writer can write the letters of the alphabet sequentially on the paper. A consumer or reader can eventually read the alphabetic characters from the paper in the order in which they were originally written.


The I/O stream process - Stream reader, stream writer and the backing store


Figure 2-1: The stream I/O process


In this chapter, you’ll discover how to perform I/O on files, memory, and networks using stream classes in the .NET Framework. We’ll start out describing the Stream class that’s inherited by several classes that allow you to perform I/O in a common way across different operating system resources. We’ll focus mostly on performing I/O on files and network resources because this is a network programming book. Once we have described the core stream classes, we’ll talk about other stream classes that interact with the core stream classes. Finally, we’ll introduce stream readers and writers that allow you to read and write formatted text and binary data types to streams.


Stream Types


There are two types of streams available:


  1. Base.
  2. Composable.


Base streams are streams that work directly with a backing store such as a file. Composable streams, on the other hand, are streams that operate on top of other streams. Composable streams by their nature have constructors that accept a stream as a parameter, whereas base streams do not. Therefore, a composable stream relies on the base stream you provide as input. For example, you can have a stream that handles encryption and decryption of data. If you want to write encrypted data to a file, you can write to a composable encryption stream that must call a base stream that’s capable of writing the data from the encryption stream to a file. You can layer as many composable streams as you want on top of one another until you reach a base stream.

Figure 2-2 shows a stream relationship diagram that describes how base streams and composable streams interact with one another. At the top of the diagram, you’ll see stream readers and writers that are essentially classes specifically designed to read or write formatted data to a stream. Stream readers and writers are described later in the chapter, and you’ll find that they make stream programming easier because they allow you to work with data using a friendlier format, such as strings. It’s important to note that the readers and writers can interact with either composable streams or base streams to perform I/O. The composable stream box shows that a composable stream must interact with a base stream to perform I/O on a system resource. A composable stream can also interact with another composable stream. The dashed line between the composable and base stream boxes illustrates this interaction. The base stream box shows that base streams are the only interfaces that actually interact directly with system resources.


.NET stream objects


Figure 2-2: Stream relationship diagram



Stream Class


The foundation for managed I/O in the .NET Framework is the Stream class. The Stream class is an abstract class that must be inherited to create either a base stream or a composable stream. The Stream class provides simple common methods to read and write data directly to a store, such as base streams, or to another stream, such as composable streams.


Basic Operations


Once you have successfully created a stream, you can begin performing I/O operations. Table 2-1 describes the main operations that are generally available in all stream classes to handle I/O. Depending on the stream at hand, it will not be possible to implement all operations for various technical reasons. If a stream does not handle a particular method, generally it will throw a NotSupportedException from the System namespace.


Table 2-1: Basic Stream Methods





Allows data to be read asynchronously from a data store.


Allows data to be written asynchronously to a data store.


Closes a stream for further I/O operations and releases any operating system resources associated with a stream.


Completes asynchronous read operations started from BeginRead().


Completes asynchronous write operations started from BeginWrite().


Forces any buffered data associated with a stream to be written to the backing store.


Allows one or more data bytes to be read from a backing store.


Allows one byte to be read from a backing store.


Allows the Position property of the stream to be set.


Allows the size of the backing store in bytes to be controlled.


Allows bytes to be written to a backing store.


Allows one byte to be written to a backing store.


Common Properties


Stream classes also have several properties that help streams work with a backing store. Table 2-2 describes the available properties.


Table 2-2: Basic Stream Properties





Determines if the stream can be read.


Determines if the stream allows you to change the Position property.


Determines if the stream can be written to.


Reports the size in bytes of the backing store.


Controls the location in a stream where the next byte will be read or written to a backing store.


Reading and writing data to stream classes is handled using byte-type arrays. This method is fine if you’re developing an application that simply deals with data in binary form. Later in this chapter, we’ll present stream reader and writer classes that allow you to read and write text data or other binary data types. For our discussions of the stream, we’ll stick to reading and writing data in byte-type form.


Synchronous and Asynchronous I/O


Our discussion of performing I/O with streams in this chapter centers on handling synchronous I/O patterns where the Read or Write stream methods will block until the I/O operation is completed with the system resource. Depending on the resource, blocking can be very limiting to your application, especially if you need to service something else, such as the user interface of your application, and are stuck waiting all day to read data from a system resource such as a network. The .NET Framework also allows you to perform I/O asynchronously. The next chapter will describe threading and the asynchronous I/O pattern, where you can use the BeginRead(), EndRead(), BeginWrite(), and EndWrite() methods to avoid blocking on I/O operations. When performing I/O on streams, you should choose between either synchronous or asynchronous I/O patterns and never mix the two styles.





Base Streams


Base streams are stream classes that perform I/O directly with operating system resources such as files, memory, and network resources. Please refer to the MSDN for base streams that are available that include: FileStream, MemoryStream, and BufferedStream.


File Stream


One of the most practical uses for streams is reading and writing data to files. The .NET Framework provides the FileStream class that is specifically designed to work with file I/O. Working with files involves creating a file or opening an existing file, reading and writing data to the file, and eventually closing the file from read and write operations.

Later in this chapter, we’ll discuss how stream readers and writers can also allow you to read or write data to a file without having to create a file stream. Readers and writers have constructors that take a path identifying a file and internally create a file stream. Unless you need more granular control of file I/O, it’s often a good idea to use readers/writers for accessing files without having to explicitly create file streams.


Creating or Opening a File


Before you can begin reading or writing data to a file, you must create a new file or open an existing file. To do so, you must supply file location information to the FileStream constructor method. Another static class in the System.IO namespace named File is also capable of creating a file stream and performing other file management tasks. For simplicity, we only show how to create a new file using the FileStream constructor. The following code fragment shows how to create a new file named Jim.dat in your current working directory and set the access control for the file to read and write:


< Chapter 2 TOC | Main | FileStream Examples >