Text IO Using Readers and Writers

Text I/O Using Readers and Writers

TextReader and TextWriter are the abstract base classes for a group of classes that are used to read and write characters. There are four classes in System::IO that derive from these two bases—StreamReader, StreamWriter, StringReader, and StringWriter—along with several other much more specialized writer classes in other namespaces.

Using TextWriter

The TextWriter class has a number of useful methods, as summarized in the following table.




Closes the writer and releases any resources it’s using


Releases all unmanaged resources used by the writer and optionally releases managed resources as well


Causes all buffered data to be written to the underlying device


Creates a thread-safe wrapper for the writer


Writes text without a newline


Writes text with a newline

As you might guess from the inclusion of the Write and WriteLine functions in the table, the Console class uses a TextWriter object to perform output.

To show you how the I/O classes work together, let’s look at how you use the StreamWriter class. Before we start, though, it’s important that you understand how the .NET Framework implements I/O. Rather than create a number of classes that each perform an end-to-end I/O task, .NET implements a number of smaller general-purpose classes that you can plug together to get the effect you want. So .NET doesn’t have a “write characters to a file” class and a “write characters to the screen” class. Instead, it has a “write characters to a byte stream” class and a “read bytes from a stream and write them to a file” class. If you plug the output from the first class into the input of the second, you end up writing characters to a file.

This model is flexible because you can take binary or character data, convert it into bytes, and then pass the bytes to any of several classes to output them to files, memory, or a string. Data is transferred between the classes as streams of bytes, a method that provides a flexible base on which to build. The basic functionality for handling byte streams is provided by the Stream class, and you can build your own specialized I/O classes on top of Stream if you need to.

With that information in mind, the following exercise will show you how to write character data to a text file using a TextWriter. Using the Plug and Play model for I/O that the .NET Framework uses, you need to create the following two objects:

  • A FileStream object that takes bytes as input and writes them to a file

  • A StreamWriter object that takes text and converts it to a byte stream

Here’s the exercise.

  1. Start a new Visual C++ Console Application (.NET) project named CppWriter.

  2. The TextWriter and file I/O classes are part of System::IO, so include a using declaration at the start of the program, like this:

    using namespace System::IO;
  3. In the _tmain function, create a FileStream object to write to a file.

    // Create a FileStream try { FileStream* fs = new FileStream(S"output.txt", FileMode::Create); } catch(System::Exception* pe) { Console::WriteLine(pe->ToString()); }

    The FileStream constructor takes a file name and a mode. In this case, the file is going to be created if it doesn’t exist and overwritten if it does. I’ve used output.txt as the file name, but you can specify any path and file name you like for the new file.


    See the section “The FileStream Class” later in this chapter for more details on how to construct FileStream objects.

    The code is enclosed in a try block because a lot of things could go wrong when trying to open this file.

  4. Create a StreamWriter that uses the FileStream, as shown here:

    try { // Create a FileStream FileStream* fs = new FileStream(S"output.txt", FileMode::Create); // Create a StreamWriter  StreamWriter* sw = new StreamWriter(fs); } catch(System::Exception* pe) { Console::WriteLine(pe->ToString()); }

    The StreamWriter constructor takes a pointer to a Stream object as its one argument.

  5. You can now use the Write and WriteLine functions to output text to the file. Put the following lines inside the try block:

    // Write some text sw->WriteLine(S"First line"); sw->WriteLine(S"Second line"); sw->WriteLine(S"Third line");
  6. Make sure that all output is flushed to the file, and close the stream.

    // Close up the file sw->Flush(); sw->Close();

    WriteLine performs buffered output, which means that it doesn’t necessarily write lines to the file every time you call the function. Instead, it maintains an internal buffer and writes the buffer to disk as necessary. One disk access per buffer is more efficient than writing individual lines, but you need to call Flush at the end of the code to make sure that output currently in the buffer is transferred to the file.

  7. Build and run the application.

    A text file named output.txt should appear in the CppWriter project directory. The file contains the three lines of text written by the CppWriter application.

The FileStream Class

FileStream is used to pass bytes from some other class—such as StreamWriter— to a file. There are several overloaded constructors to this class that let you specify combinations of the following:

  • The file name

  • The file mode, which determines how the file is going to be opened

  • The type of access required

  • The sharing options

The file mode is represented by members of the FileMode enumeration, which are described in the following table.




Opens an existing file, or creates a new file and appends text to the end.


Creates a new file, or opens an existing one and overwrites it.


Creates a new file, throwing an exception if the file already exists.


Opens an existing file.


Opens an existing file, or creates a new one.


Opens an existing file, and truncates its size to 0 bytes. An exception will be thrown if the file doesn’t exist.

The access is represented by members of the FileAccess enumeration, as listed in the following table.




Represents read access


Represents read/write access


Represents write access

Similarly, the sharing access is specified by the FileShare enumeration, as listed in the following table.




No sharing


Represents shared read access


Represents shared read/write access


Represents shared write access

The following example shows how to construct a FileStream using these permissions:

FileStream* fs2 = new FileStream( S"foo.txt", // the filename FileMode::Create, // create or overwrite FileAccess::ReadWrite, // request read/write access FileShare::Read); // allow shared reading

Although you’ll usually use the FileStream class with other writer classes, you can use its Read and Write methods to input and output bytes directly.

Using TextReader

The structure and operation of the TextReader class parallels that of TextWriter. The following table lists the methods provided for you by TextReader.




Closes the reader, and releases any resources it’s using


Releases all unmanaged resources used by the reader, and optionally releases managed resources as well


Returns the next character from the input stream without removing it


Reads one or more characters from the input stream


Reads a block of characters


Reads a line


Reads to the end of the input stream


Provides a thread-safe wrapper for TextReader objects

As with TextWriter, you use TextReader by plugging a reader into an object that is going to act as a source of bytes. There are several of these, including the one you’ve already met, FileStream.

The exercise that follows shows you how to write a program similar in functionality to the UNIX more command, which will read a file and echo its contents to the screen a few lines at a time. After it has displayed some lines, the user has the choice of pressing the Enter key to continue or pressing Q to quit.

  1. Start a new Visual C++ Console Application (.NET) project named CppReader.

  2. Include a using declaration for System::IO at the top of the project.

    using namespace System::IO;
  3. Because the user is going to enter the name of the file to be listed on the command line, change the declaration of the _tmain function to include the command line parameter arguments, as shown here:

    int _tmain(int argc, char* argv[])

    If you haven’t met command-line arguments in a C or C++ program before, the _tmain function can optionally take two arguments. The first—traditionally named argc—is the number of command-line arguments, and the second—traditionally named argv—is an array of strings.

  4. Add code to _tmain to ensure that the user has entered a file name.

    // Check for required arguments if (argc < 2) { Console::WriteLine(S"Usage: CppReader path"); return 0; } String* path = new String(argv[1]);

    If the user hasn’t given two arguments, an error message is printed and the program exits. If the user has, the second argument is saved for later use.

  5. It’s wise to check that the path represents an existing file before continuing, so add this code:

    if (!File::Exists(path)) { Console::WriteLine(S"Invalid filename!"); return -1; }

    The File::Exists method checks whether a file with the specified name exists, returning false if it doesn’t. It will also return false if you give the name of a directory rather than a file. Note the return value of -1. It’s a common convention for C/C++ applications to return 0 to indicate success, with negative values being used to denote error conditions.

  6. Start to list the file. The first step is to create a FileStream and then connect it to a StreamReader.

    try { FileStream* fs = new FileStream(path, FileMode::Open); StreamReader* sr = new StreamReader(fs); } catch(System::Exception* pe) { Console::WriteLine(pe->ToString()); }

    In this case, you’re opening the file using FileMode::Open, which will throw an exception if the file doesn’t already exist.

  7. Listing the file is done in this loop, which you should place after creating the StreamReader object, like this:

    int count = 0; for(;;) { String* line = sr->ReadLine(); count++; // If there are no more lines, break out of the loop if (line == 0) break; Console::WriteLine(line); if (count % 20 == 0) { Console::Write(S"--more--"); String* response = Console::ReadLine(); if (response->Equals(S"q")) break; count = 0; } } Console::WriteLine("-- end --");

    The count variable is going to be used to count the lines as they’re read so that the program knows where to break. The loop reads a line into a String using the ReadLine function of StreamReader; if there are no more lines to read, a null pointer will be returned. The line is then echoed to the console and the count checked. I’ve set the number of lines displayed at one time to an arbitrary value of 20; when the count is exactly divisible by 20, the program writes “-- more--” to the console and waits for the user to input something. If the user presses a lowercase q, the program stops; otherwise, it outputs the next set of lines.

  8. Build and run the program, giving the name of a suitable text file as the argument.

Microsoft Visual C++  .NET(c) Step by Step
Microsoft Visual C++ .NET(c) Step by Step
ISBN: 735615675
Year: 2003
Pages: 208

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net