21.2 Reading and Writing Data

Reading and
writing data is accomplished with the
Stream class. Remember streams? This is a
chapter about streams.[1]

[1] With a tip of the hat to Arlo
Guthrie.

Stream supports synchronous and asynchronous reads
and writes. The .NET Framework provides a number of classes derived
from Stream, including
FileStream, MemoryStream, and
NetworkStream. In addition, there is a
BufferedStream class, which provides buffered I/O
and which can be used in conjunction with any of the other stream
classes. The principal classes involved with
I/O are summarized in Table 21-5.

Table 21-5. Principle I/O classes of the .NET Framework

Class

Use

Stream

Abstract class that supports reading and writing bytes.

BinaryReader/BinaryWriter

Read and write encoded strings and primitive datatypes to and from
streams.

File,
FileInfo,
Directory,
DirectoryInfo

Provide implementations for the abstract
FileSystemInfo classes, including creating,
moving, renaming, and deleting files and directories.

TextReader and TextWriter are
abstract classes designed for Unicode character I/O.
StringReader and StringWriter
write to and from strings, allowing your input and output to be
either a stream or a string.

BufferedStream

A stream that adds buffering to another stream such as a
NetworkStream. Note that
FileStream has buffering built in.
BufferedStreams can improve performance of the
stream to which they are attached.

MemoryStream

A nonbuffered stream whose encapsulated data is directly accessible
in memory. A MemoryStream has no backing store,
and is most useful as a temporary buffer.

NetworkStream

A stream over a network connection.

21.2.1 Binary Files

This
section starts by using the basic
Stream class to perform a binary read of a file.
The term binaryread is
used to distinguish from a textread. If you don't know for
certain that a file is just text, it is safest to treat it as a
stream of bytes, known as a binary file.

The Stream class is chock-a-block with methods,
but the most important are Read(
), Write(
),
BeginRead( ), BeginWrite(
), and Flush(
). All of these are covered in the next
few sections.

To perform a binary read, begin by creating a pair of
Stream objects, one for reading and one for
writing:

To open the files to read and write, use the
static
OpenRead( ) and OpenWrite(
) methods of the File
class. The static overload of these methods takes the path for the
file as an argument, as shown previously.

Binary reads work by reading into a buffer. A buffer is just an array
of bytes that will hold the data read by the Read(
) method.

Pass in the buffer, the offset in the buffer at which to begin
storing the data read in, and the number of bytes to read.
InputStream.Read reads bytes from the backing
store into the buffer and returns the total number of bytes read.

Each buffer-ful of bytes is written to the output file. The arguments
to Write( ) are the buffer from which to read, the
offset into that buffer at which to start reading, and the number of
bytes to write. Notice that you write the same number of bytes as you
just read.

The result of running this program is that a copy of the input file
(test1.cs) is made in the same
directory and named test1.bak.

21.2.2 Buffered Streams

In the
previous example, you created a buffer to read into. When you called
Read( ), a buffer-ful was read from disk. It might
be, however, that the operating system can be much more efficient if
it reads a larger (or smaller) number of bytes at once.

A bufferedstream
object allows the operating system to
create its own internal buffer, and read bytes to and from the
backing store in whatever increments it thinks is most efficient. It
will still fill your buffer in the increments you dictate, but your
buffer is filled from the in-memory buffer, not from the backing
store. The net effect is that the input and output are more efficient
and thus faster.

A BufferedStream object is composed around an existing
Stream object that you already have created. To
use a BufferedStream, start by creating a normal
stream class as you did in Example 21-4:

With larger files, this example should run more quickly than Example 21-4 did.

21.2.3 Working with Text Files

If you know that the file you are
reading (and writing) contains nothing but text, you might want to
use the
StreamReader and
StreamWriter classes. These classes are designed to
make manipulation of text easier. For example, they support the
ReadLine( ) and WriteLine(
) methods that read and write a line of
text at a time. You've used WriteLine(
) with the Console object.

To create a StreamReader instance, start by
creating a FileInfo object and then call the
OpenText( ) method on that object:

The second parameter is the Boolean argument
append. If the file already exists,
true will cause the new data to be appended to the
end of the file, and false will cause the file to
be overwritten. In this case, pass in false,
overwriting the file if it exists.

You can now create a loop to write out the contents of each line of
the old file into the new file, and while you're at
it, to print the line to the console as well:

Example 21-6. Reading and writing to a text file

namespace Programming_CSharp
{
using System;
using System.IO;
class Tester
{
public static void Main( )
{
// make an instance and run it
Tester t = new Tester( );
t.Run( );
}
// Set it running with a directory name
private void Run( )
{
// open a file
FileInfo theSourceFile = new FileInfo(
@"C:\test\source\test.cs");
// create a text reader for that file
StreamReader reader = theSourceFile.OpenText( );
// create a text writer to the new file
StreamWriter writer = new StreamWriter(
@"C:\test\source\test.bak",false);
// create a text variable to hold each line
string text;
// walk the file and read every line
// writing both to the console
// and to the file
do
{
text = reader.ReadLine( );
writer.WriteLine(text);
Console.WriteLine(text);
} while (text != null);
// tidy up
reader.Close( );
writer.Close( );
}
}
}

When this program is run, the contents of the original file are
written both to the screen and to the new file. Notice the syntax for
writing to the console:

Console.WriteLine(text);

This syntax is nearly identical to that used to write to the file:

writer.WriteLine(text);

The key difference is that the WriteLine( ) method
of Console is static, while the
WriteLine( ) method of
StreamWriter, which is inherited from
TextWriter, is an instance method, and thus must
be called on an object rather than on the class itself.