Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

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

.pdf
Скачиваний:
111
Добавлен:
16.08.2013
Размер:
10.35 Mб
Скачать
Note

534 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

Figure 16-7. Readers and writers

To aid in your understanding of the core writing capabilities of the StreamWriter and StringWriter classes, Table 16-7 describes the core members of the abstract TextWriter base class.

Table 16-7. Core Members of TextWriter

Member

Meaning in Life

Close()

Closes the writer and frees any associated resources. In the process, the buffer is

 

automatically flushed.

Flush()

Clears all buffers for the current writer and causes any buffered data to be

 

written to the underlying device, but does not close the writer.

NewLine

Indicates the newline constant for the derived writer class. The default line

 

terminator is a carriage return followed by a line feed (\r\n).

Write()

Writes a line to the text stream without a newline constant.

WriteLine()

Writes a line to the text stream with a newline constant.

 

 

 

 

The last two members of the TextWriter class probably look familiar to you. If you recall, the System. Console type has Write() and WriteLine() members that push textual data to the standard output device. In fact, the Console.In property wraps a TextWriter, and the Console.Out property wraps a TextReader.

The derived StreamWriter class provides an appropriate implementation for the Write(), Close(), and Flush() methods, and it defines the additional AutoFlush property. This property, when set to true, forces StreamWriter to flush all data every time you perform a write operation. Be aware that you can gain better performance by setting AutoFlush to false, provided you always call Close() when you are done writing with a StreamWriter.

Writing to a Text File

Now for an example of working with the StreamWriter type. The following class creates a new file named reminders.txt using the File.CreateText() method. Using the obtained StreamWriter object, you add some textual data to the new file, as shown here:

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:

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

537

static void Main(string[] args)

{

Console.WriteLine("***** Fun with StringWriter / StringReader *****\n");

//Create a StringWriter and emit character data to memory.

StringWriter strWriter = new StringWriter(); strWriter.WriteLine("Don't forget Mother's Day this year..."); strWriter.Close();

//Get a copy of the contents (stored in a string) and pump

//to console.

Console.WriteLine("Contents of StringWriter:\n{0}", strWriter);

}

Because StringWriter and StreamWriter both derive from the same base class (TextWriter), the writing logic is more or less identical. However, given that nature of StringWriter, be aware that this class allows you to extract a System.Text.StringBuilder object via the GetStringBuilder() method:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with StringWriter / StringReader *****\n");

// Create a StringWriter and emit character data to memory.

StringWriter strWriter = new StringWriter();

...

// Get the internal StringBuilder.

StringBuilder sb = strWriter.GetStringBuilder(); sb.Insert(0, "Hey!! ");

Console.WriteLine("-> {0}", sb.ToString()); sb.Remove(0, "Hey!! ".Length); Console.WriteLine("-> {0}", sb.ToString());

}

When you wish to read from a stream of character data, make use of the corresponding StringReader type, which (as you would expect) functions identically to the related StreamReader class. In fact, the StringReader class does nothing more than override the inherited members to read from a block of character data, rather than a file, as shown here:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with StringWriter / StringReader *****\n");

// Create a StringWriter and emit character data to memory.

StringWriter strWriter = new StringWriter();

...

// Read data from the StringWriter.

StringReader strReader = new StringReader(writer.ToString()); string input = null;

while ((input = strReader.ReadLine()) != null)

{

Console.WriteLine (input);

}

strReader.Close();

}

Source Code The StringReaderWriterApp is included under the Chapter 16 subdirectory.

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' };

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

539

// Write the data bw.Write(aDouble); bw.Write(anInt); bw.Write(aCharArray); bw.Close();

}

Notice how the FileStream object returned from FileInfo.OpenWrite() is passed to the constructor of the BinaryWriter type. Using this technique, it is very simple to “layer in” a stream before writing out the data. Do understand that the constructor of BinaryWriter takes any Stream-derived type (e.g., FileStream, MemoryStream, or BufferedStream). Thus, if you would rather write binary data to memory, simply supply a valid MemoryStream object.

To read the data out of the BinFile.dat file, the BinaryReader type provides a number of options. Here, you will make use of PeekChar() to determine if the stream still has data to provide and, if so, use ReadByte() to obtain the value. Note that you are formatting the bytes in hexadecimal and inserting seven spaces between each:

static void Main(string[] args)

{

// Open a binary writer for a file.

FileInfo f = new FileInfo("BinFile.dat");

...

// Read the data as raw bytes

BinaryReader br = new BinaryReader(f.OpenRead()); int temp = 0;

while (br.PeekChar() != -1)

{

Console.Write("{0,7:x} ", br.ReadByte()); if (++temp == 4)

{

// Write a new line every 4 bytes

Console.WriteLine(); temp = 0;

}

}

Console.WriteLine();

}

The output of this program appears in Figure 16-9.

Figure 16-9. Reading bytes from a binary file

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;

}

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

541

//Set up the things to be on the

//lookout for.

watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite

| NotifyFilters.FileName

|NotifyFilters.DirectoryName;

//Only watch text files. watcher.Filter = "*.txt";

//Add event handlers.

watcher.Changed += new FileSystemEventHandler(OnChanged); watcher.Created += new FileSystemEventHandler(OnChanged); watcher.Deleted += new FileSystemEventHandler(OnChanged); watcher.Renamed += new RenamedEventHandler(OnRenamed);

//Begin watching the directory. watcher.EnableRaisingEvents = true;

//Wait for the user to quit the program.

Console.WriteLine(@"Press 'q' to quit app."); while(Console.Read()!='q');

}

The two event handlers simply print out the current file modification:

static void OnChanged(object source, FileSystemEventArgs e)

{

// Specify what is done when a file is changed, created, or deleted.

Console.WriteLine("File: {0} {1}!", e.FullPath, e.ChangeType);

}

static void OnRenamed(object source, RenamedEventArgs e)

{

// Specify what is done when a file is renamed.

Console.WriteLine("File: {0} renamed to\n{1}", e.OldFullPath, e.FullPath);

}

To test this program, run the application and open Windows Explorer. Try renaming your files, creating a *.txt file, deleting a *.txt file, or whatnot. You will see the console application print out various bits of information regarding the state of the text files within MyFolder (see Figure 16-10).

Figure 16-10. Watching some text files

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.