
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf

C H A P T E R 1 6 ■ T H E S YS T E M . I O N A M E S PA C E |
535 |
static void Main(string[] args)
{
Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");
//Get a StreamWriter and write string data.
StreamWriter writer = File.CreateText("reminders.txt"); writer.WriteLine("Don't forget Mother's Day this year..."); writer.WriteLine("Don't forget Father's Day this year..."); writer.WriteLine("Don't forget these numbers:");
for(int i = 0; i < 10; i++) writer.Write(i + " ");
//Insert a new line.
writer.Write(writer.NewLine);
// Closing automatically flushes! writer.Close();
Console.WriteLine("Created file and wrote some thoughts...");
}
Once you run this program, you can examine the contents of this new file (see Figure 16-8).
Figure 16-8. The contents of your *.txt file
Reading from a Text File
Now you need to understand how to programmatically read data from a file using the corresponding StreamReader type. As you recall, this class derives from TextReader, which offers the functionality described in Table 16-8.
Table 16-8. TextReader Core Members
Member |
Meaning in Life |
Peek() |
Returns the next available character without actually changing the position of |
|
the reader. A value of –1 indicates you are at the end of the stream. |
Read() |
Reads data from an input stream. |
ReadBlock() |
Reads a maximum of count characters from the current stream and writes the |
|
data to a buffer, beginning at index. |
ReadLine() |
Reads a line of characters from the current stream and returns the data as |
|
a string (a null string indicates EOF). |
ReadToEnd() |
Reads all characters from the current position to the end of the stream and |
|
returns them as a single string. |
|
|
If you now extend the current MyStreamWriterReader class to use a StreamReader, you can read in the textual data from the reminders.txt file as shown here:

536C H A P T E R 1 6 ■ T H E S YS T E M . I O N A M E S PA C E
static void Main(string[] args)
{
Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");
...
// Now read data from file.
Console.WriteLine("Here are your thoughts:\n"); StreamReader sr = File.OpenText("reminders.txt");
string input = null;
while ((input = sr.ReadLine()) != null)
{
Console.WriteLine (input);
}
}
Once you run the program, you will see the character data within Thoughts.txt displayed to the console.
Directly Creating StreamWriter/StreamReader Types
One of the slightly confusing aspects of working with the types within System.IO is that you can often achieve an identical result using numerous approaches. For example, you have already seen that you can obtain a StreamWriter via the File or FileInfo type using the CreateText() method. In reality, there is yet another way in which you can work with StreamWriters and StreamReaders: create them directly. For example, the current application could be retrofitted as so:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");
// Get a StreamWriter and write string data.
StreamWriter writer = new StreamWriter("reminders.txt");
...
// Now read data from file.
StreamReader sr = new StreamReader("reminders.txt");
...
}
Although it can be a bit confusing to see so many seemingly identical approaches to file I/O, keep in mind that the end result is greater flexibility. In any case, now that you have seen how to move character data to and from a given file using the StreamWriter and StreamReader types, you will next examine the role of the StringWriter and StringReader classes.
■Source Code The StreamWriterReaderApp project is included under the Chapter 16 subdirectory.
Working with StringWriters and StringReaders
Using the StringWriter and StringReader types, you can treat textual information as a stream of in-memory characters. This can prove helpful when you wish to append character-based information to an underlying buffer. To illustrate, the following example writes a block of string data to a StringWriter object rather than a file on the local hard drive:


538 C H A P T E R 1 6 ■ T H E S YS T E M . I O N A M E S PA C E
Working with BinaryWriters and BinaryReaders
The final writer/reader sets you will examine here are BinaryReader and BinaryWriter, both of which derive directly from System.Object. These types allow you to read and write discrete data types to an underlying stream in a compact binary format. The BinaryWriter class defines a highly overloaded Write() method to place a data type in the underlying stream. In addition to Write(), BinaryWriter provides additional members that allow you to get or set the Stream-derived type and offers support for random access to the data (see Table 16-9).
Table 16-9. BinaryWriter Core Members
Member |
Meaning in Life |
BaseStream |
This read-only property provides access to the underlying stream used with the |
|
BinaryWriter object. |
Close() |
This method closes the binary stream. |
Flush() |
This method flushes the binary stream. |
Seek() |
This method sets the position in the current stream. |
Write() |
This method writes a value to the current stream. |
|
|
The BinaryReader class complements the functionality offered by BinaryWriter with the members described in Table 16-10.
Table 16-10. BinaryReader Core Members
Member |
Meaning in Life |
BaseStream |
This read-only property provides access to the underlying stream used with the |
|
BinaryReader object. |
Close() |
This method closes the binary reader. |
PeekChar() |
This method returns the next available character without actually advancing the |
|
position in the stream. |
Read() |
This method reads a given set of bytes or characters and stores them in the |
|
incoming array. |
ReadXXXX() |
The BinaryReader class defines numerous ReadXXXX() methods that grab the next |
|
type from the stream (ReadBoolean(), ReadByte(), ReadInt32(), and so forth). |
|
|
The following example writes a number of data types to a new *.dat file:
static void Main(string[] args)
{
//Open a binary writer for a file.
FileInfo f = new FileInfo("BinFile.dat"); BinaryWriter bw = new BinaryWriter(f.OpenWrite());
//Print out the type of BaseStream.
//(System.IO.FileStream in this case).
Console.WriteLine("Base stream is: {0}", bw.BaseStream);
//Create some data to save in the file
double aDouble = 1234.67; int anInt = 34567;
char[] aCharArray = { 'A', 'B', 'C' };


540 C H A P T E R 1 6 ■ T H E S YS T E M . I O N A M E S PA C E
■Source Code The BinaryWriterReader application is included under the Chapter 16 subdirectory.
Programmatically “Watching” Files
Now that you have a better handle on the use of various readers and writers, next you’ll look at the role of the FileSystemWatcher class. This type can be quite helpful when you wish to programmatically monitor (or “watch”) files on your system. Specifically, the FileSystemWatcher type can be instructed to monitor files for any of the actions specified by the NotifyFilters enumeration (while many of these members are self-explanatory, check the online help for further details):
public enum System.IO.NotifyFilters
{
Attributes, CreationTime,
DirectoryName, FileName, LastAccess, LastWrite, Security, Size,
}
The first step you will need to take to work with the FileSystemWatcher type is to set the Path property to specify the name (and location) of the directory that contains the files to be monitored, as well as the Filter property that defines the file extensions of the files to be monitored.
At this point, you may choose to handle the Changed, Created, and Deleted events, all of which work in conjunction with the FileSystemEventHandler delegate. This delegate can call any method matching the following pattern:
//The FileSystemEventHandler delegate must point
//to methods matching the following signature.
void MyNotificationHandler(object source, FileSystemEventArgs e)
As well, the Renamed event may also be handled via the RenamedEventHandler delegate type, which can call methods matching the following signature:
//The RenamedEventHandler delegate must point
//to methods matching the following signature.
void MyNotificationHandler(object source, RenamedEventArgs e)
To illustrate the process of watching a file, assume you have created a new directory on your C drive named MyFolder that contains various *.txt files (named whatever you wish). The following console application will monitor the *.txt files within the MyFolder and print out messages in the event that the files are created, deleted, modified, or renamed:
static void Main(string[] args)
{
Console.WriteLine("***** The Amazing File Watcher App *****\n");
// Establish the path to the directory to watch.
FileSystemWatcher watcher = new FileSystemWatcher(); try{
watcher.Path = @"C:\MyFolder";
}
catch(ArgumentException ex) { Console.WriteLine(ex.Message); return;
}


542 C H A P T E R 1 6 ■ T H E S YS T E M . I O N A M E S PA C E
■Source Code The MyDirectoryWatcher application is included under the Chapter 16 subdirectory.
Performing Asynchronous File I/O
To conclude our examination of the System.IO namespace, let’s see how to interact with FileStream types asynchronously. You have already seen the asynchronous support provided by the .NET Framework during the examination of multithreading (see Chapter 14). Because I/O can be a lengthy task, all types deriving from System.IO.Stream inherit a set of methods that enable asynchronous processing of the data. As you would expect, these methods work in conjunction with the IAsyncResult type:
public abstract class System.IO.Stream : MarshalByRefObject,
IDisposable
{
...
public virtual IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state);
public virtual IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state);
public virtual int EndRead(IAsyncResult asyncResult); public virtual void EndWrite(IAsyncResult asyncResult);
}
The process of working with the asynchronous behavior of Stream-derived types is identical to working with asynchronous delegates and asynchronous remote method invocations. While it’s unlikely that asynchronous behaviors will greatly improve file access, other streams (e.g., socket based) are much more likely to benefit from asynchronous handling. In any case, the following example illustrates one manner in which you can asynchronously interact with a FileStream type:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main thread started. ThreadID = {0}", Thread.CurrentThread.GetHashCode());
//Must use this ctor to get a FileStream with asynchronous
//read or write access.
FileStream fs = new FileStream("logfile.txt", FileMode.Append,
FileAccess.Write, FileShare.None, 4096, true);
string msg = "this is a test";
byte[] buffer = Encoding.ASCII.GetBytes(msg);
//Start the asynchronous write. WriteDone invoked when finished.
//Note that the FileStream object is passed as state info to the
//callback method.
fs.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(WriteDone), fs);
}

C H A P T E R 1 6 ■ T H E S YS T E M . I O N A M E S PA C E |
543 |
private static void WriteDone(IAsyncResult ar)
{
Console.WriteLine("AsyncCallback method on ThreadID = {0}", Thread.CurrentThread.GetHashCode());
Stream s = (Stream)ar.AsyncState; s.EndWrite(ar);
s.Close();
}
}
The only point of interest in this example (assuming you recall the process of working with delegates!) is that in order to enable the asynchronous behavior of the FileStream type, you must make use of a specific constructor (shown here). The final System.Boolean parameter (when set to true) informs the FileStream object to perform its work on a secondary thread of execution.
■Source Code The AsyncFileStream application is included under the Chapter 16 subdirectory.
Summary
This chapter began by examining the use of the Directory(Info) and File(Info) types (including several new members of the File type brought about with .NET 2.0). As you learned, these classes allow you to manipulate a physical file or directory on your hard drive. Next, you examined a number of types derived from the abstract Stream class, specifically FileStream. Given that Stream-derived types operate on a raw stream of bytes, the System.IO namespace provides numerous reader/writer types (StreamWriter, StringWriter, BinaryWriter, etc.) that simplify the process.
Along the way, you also checked out a new I/O-centric type provided by .NET 2.0 (DriveType), and you learned how to monitor files using the FileSystemWatcher type and how to interact with streams in an asynchronous manner.