Tải bản đầy đủ (.pdf) (140 trang)

Pro C# 2008 and the .NET 3.5 Platform, Fourth Edition phần 6 docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (4.55 MB, 140 trang )

FileInfo f3 = new FileInfo(@"C:\Test3.dat");
using(FileStream readOnlyStream = f3.OpenRead())
{
// Use the FileStream object
}
// Now get a FileStream object with write-only permissions.
FileInfo f4 = new FileInfo(@"C:\Test4.dat");
using(FileStream writeOnlyStream = f4.OpenWrite())
{
// Use the FileStream object
}
}
The FileInfo.OpenText() Method
Another open-centric member of the FileInfo type is OpenText(). Unlike Create(), Open(),
OpenRead(), and OpenWrite(), the OpenText() method returns an instance of the StreamReader type,
rather than a
FileStream type. Assuming you have a file named boot.ini on your C drive, the follow-
ing would be one manner to gain access to its contents:
static void Main(string[] args)
{
// Get a StreamReader object.
FileInfo f5 = new FileInfo(@"C:\boot.ini");
using(StreamReader sreader = f5.
OpenText())
{
// Use the StreamReader object
}
}
As you will see shortly, the StreamReader type provides a way to read character data from the
underlying file.
The FileInfo.CreateText() and FileInfo.AppendText() Methods


The final two methods of interest at this point are CreateText() and AppendText(), both of which
return a
StreamWriter reference, as shown here:
static void Main(string[] args)
{
FileInfo f6 = new FileInfo(@"C:\Test5.txt");
using(StreamWriter swriter = f6.CreateText())
{
// Use the StreamWriter object
}
FileInfo f7 = new FileInfo(@"C:\FinalTest.txt");
using(StreamWriter swriterAppend = f7.AppendText())
{
// Use the StreamWriter object
}
}
As you would guess, the StreamWriter type provides a way to write character data to the
underlying file.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE672
8849CH20.qxd 10/17/07 5:58 PM Page 672
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Working with the File Type
The File type provides functionality almost identical to that of the FileInfo type, using a number
of static members. Like
FileInfo, File supplies AppendText(), Create(), CreateText(), Open(),
OpenRead(), OpenWrite(), and OpenText() methods. In fact, in many cases, the File and FileInfo
types may be used interchangeably. To illustrate, each of the previous FileStream examples can be
simplified by using the
File type instead:

static void Main(string[] args)
{
// Obtain FileStream object via File.Create().
using(FileStream fs = File.Create(@"C:\Test.dat"))
{
}
// Obtain FileStream object via File.Open().
using(FileStream fs2 = File.Open(@"C:\Test2.dat",
FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.None))
{
}
// Get a FileStream object with read-only permissions.
using(FileStream readOnlyStream = File.OpenRead(@"Test3.dat"))
{
}
// Get a FileStream object with write-only permissions.
using(FileStream writeOnlyStream = File.OpenWrite(@"Test4.dat"))
{
}
// Get a StreamReader object.
using(StreamReader sreader = File.OpenText(@"C:\boot.ini"))
{
}
// Get some StreamWriters.
using(StreamWriter swriter = File.CreateText(@"C:\Test3.txt"))
{
}
using(StreamWriter swriterAppend = File.AppendText(@"C:\FinalTest.txt"))
{

}
}
Additional File-centric Members
The File type also supports a few unique members shown in Table 20-6, which can greatly simplify
the processes of reading and writing textual data.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 673
8849CH20.qxd 10/17/07 5:58 PM Page 673
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Table 20-6. Methods of the File Type
Method Meaning in Life
ReadAllBytes() Opens the specified file, returns the binary data as an array of bytes, and then
closes the file
ReadAllLines() Opens a specified file, returns the character data as an array of strings, and
then closes the file
ReadAllText() Opens a specified file, returns the character data as a System.String, and then
closes the file
WriteAllBytes() Opens the specified file, writes out the byte array, and then closes the file
WriteAllLines() Opens a specified file, writes out an array of strings, and then closes the file
WriteAllText() Opens a specified file, writes the character data, and then closes the file
Using these new methods of the
File type, you are able to read and write batches of data in just
a few lines of code. Even better, each of these new members automatically closes down the under-
lying file handle. For example, the following console program (named SimpleFileIO) will persist the
string data into a new file on the C drive (and read it into memory) with minimal fuss:
using System;
using System.IO;
class Program
{
static void Main(string[] args)

{
Console.WriteLine("***** Simple IO with the File Type *****\n");
string[] myTasks = {
"Fix bathroom sink", "Call Dave",
"Call Mom and Dad", "Play Xbox 360"};
// Write out all data to file on C drive.
File.WriteAllLines(@"C:\tasks.txt", myTasks);
// Read it all back and print out.
foreach (string task in File.ReadAllLines(@"C:\tasks.txt"))
{
Console.WriteLine("TODO: {0}", task);
}
Console.ReadLine();
}
}
Clearly, when you wish to quickly obtain a file handle, the File type will save you some key-
strokes. However, one benefit of first creating a
FileInfo object is that you are able to investigate the
file using the members of the abstract
FileSystemInfo base class.
■Source Code The SimpleFileIO project is located under the Chapter 20 subdirectory.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE674
8849CH20.qxd 10/17/07 5:58 PM Page 674
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
The Abstract Stream Class
At this point, you have seen numerous ways to obtain FileStream, StreamReader, and StreamWriter
objects, but you have yet to read data from, or write data to, a file using these types. To understand
how to do so, you’ll need to become familiar with the concept of a stream. In the world of I/O
manipulation, a

stream represents a chunk of data flowing between a source and a destination.
Streams provide a common way to interact with
a sequence of bytes, regardless of what kind of
device (file, network connection, printer, etc.) is storing or displaying the bytes in question.
The abstract
System.IO.Stream class defines a number of members that provide support for
synchronous and asynchronous interactions with the storage medium (e.g., an underlying file or
memory location). Figure 20-6 shows various descendents of the
Stream type, seen through the
eyes of the Visual Studio 2008 Object Browser.
Figure 20-6. Stream-derived types
■Note Be aware that the concept of a stream is not limited to files IO. To be sure, the .NET libraries provide
stream access to networks, memory locations, and other stream-centric abstractions.
Again, Stream descendents represent data as a raw stream of bytes; therefore, working directly
with raw streams can be quite cryptic. Some
Stream-derived types support seeking, which refers to
the process of obtaining and adjusting the current position in the stream. To begin understanding
the functionality provided by the
Stream class, take note of the core members described in
Table 20-7.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 675
8849CH20.qxd 10/17/07 5:58 PM Page 675
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Table 20-7. Abstract Stream Members
Member Meaning in Life
CanRead, CanWrite Determine whether the current stream supports reading, seeking, and/or
CanSeek writing.
Close() Closes the current stream and releases any resources (such as sockets and
file handles) associated with the current stream. Internally, this method

is aliased to the
Dispose() method; therefore “closing a stream” is
functionally equivalent to “disposing a stream.”
Flush() Updates the underlying data source or repository with the current state of
the buffer and then clears the buffer. If a stream does not implement a
buffer, this method does nothing.
Length Returns the length of the stream, in bytes.
Position Determines the position in the current stream.
Read(), ReadByte() Read a sequence of bytes (or a single byte) from the current stream and
advance the current position in the stream by the number of bytes read.
Seek() Sets the position in the current stream.
SetLength() Sets the length of the current stream.
Write(), WriteByte() Write a sequence of bytes (or a single byte) to the current stream and
advance the current position in this stream by the number of bytes
written.
Working with FileStreams
The FileStream class provides an implementation for the abstract Stream members in a manner
appropriate for file-based streaming. It is a fairly primitive stream; it can read or write only a single
byte or an array of bytes. In reality, you will not often need to directly interact with the members of
the
FileStream type. Rather, you will most likely make use of various stream wrappers, which make
it easier to work with textual data or .NET types. Nevertheless, for illustrative purposes, let’s experi-
ment with the synchronous read/write capabilities of the
FileStream type.
Assume you have a new Console Application named FileStreamApp. Your goal is to write a sim-
ple text message to a new file named
myMessage.dat. However, given that FileStream can operate
only on raw bytes, you will be required to encode the
System.String type into a corresponding byte
array. Luckily, the

System.Text namespace defines a type named Encoding, which provides members
that encode and decode strings to (or from) an array of bytes (check out the .NET Framework 3.5
SDK documentation for full details of the
Encoding type).
Once encoded, the byte array is persisted to file using the
FileStream.Write() method. To read
the bytes back into memory, you must reset the internal position of the stream (via the
Position
property) and call the ReadByte() method. Finally, you display the raw byte array and the decoded
string to the console. Here is the complete
Main() method:
// Don't forget to import the System.Text and System.IO namespaces.
static void Main(string[] args)
{
Console.WriteLine("***** Fun with FileStreams *****\n");
// Obtain a FileStream object.
using(FileStream fStream = File.Open(@"C:\myMessage.dat",
FileMode.Create))
{
// Encode a string as an array of bytes.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE676
8849CH20.qxd 10/17/07 5:58 PM Page 676
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
string msg = "Hello!";
byte[] msgAsByteArray = Encoding.Default.GetBytes(msg);
// Write byte[] to file.
fStream.Write(msgAsByteArray, 0, msgAsByteArray.Length);
// Reset internal position of stream.
fStream.Position = 0;

// Read the types from file and display to console.
Console.Write("Your message as an array of bytes: ");
byte[] bytesFromFile = new byte[msgAsByteArray.Length];
for (int i = 0; i < msgAsByteArray.Length; i++)
{
bytesFromFile[i] = (byte)fStream.ReadByte();
Console.Write(bytesFromFile[i]);
}
// Display decoded messages.
Console.Write("\nDecoded Message: ");
Console.WriteLine(Encoding.Default.GetString(bytesFromFile));
}
Console.ReadLine();
}
While this example does indeed populate the file with data, it punctuates the major downfall
of working directly with the
FileStream type: it demands to operate on raw bytes. Other Stream-
derived types operate in a similar manner. For example, if you wish to write a sequence of bytes to a
region of memory, you can allocate a
MemoryStream. Likewise, if you wish to push an array of bytes
through a network connection, you can make use of the
NetworkStream type.
As mentioned, the
System.IO namespace thankfully provides a number of “reader” and “writer”
types that encapsulate the details of working with
Stream-derived types.
■Source Code The FileStreamApp project is included under the Chapter 20 subdirectory.
Working with StreamWriters and StreamReaders
The StreamWriter and StreamReader classes are useful whenever you need to read or write charac-
ter-based data (e.g., strings). Both of these types work by default with Unicode characters; however,

you can change this by supplying a properly configured
System.Text.Encoding object reference. To
keep things simple, let’s assume that the default Unicode encoding fits the bill.
StreamReader derives from an abstract type named TextReader, as does the related
StringReader type (discussed later in this chapter). The TextReader base class provides a very lim-
ited set of functionality to each of these descendents, specifically the ability to read and peek into a
character stream.
The
StreamWriter type (as well as StringWriter, also examined later in this chapter) derives
from an abstract base class named
TextWriter. This class defines members that allow derived types
to write textual data to a given character stream.
To aid in your understanding of the core writing capabilities of the
StreamWriter and
StringWriter classes, Table 20-8 describes the core members of the abstract TextWriter base
class.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 677
8849CH20.qxd 10/17/07 5:58 PM Page 677
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Table 20-8. Core Members of TextWriter
Member Meaning in Life
Close() This method closes the writer and frees any associated resources. In the process,
the buffer is automatically flushed (again, this member is functionally equivalent
to calling the
Dispose() method).
Flush() This method 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 This property indicates the newline constant for the derived writer class. The
default line terminator for the Windows OS is a carriage return followed by a line

feed (
\r\n).
Write() This overloaded method writes data to the text stream without a newline constant.
WriteLine() This overloaded method writes data to the text stream with a newline constant.
■Note 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
To see the StreamWriter type in action, create a new Console Application named
StreamWriterReaderApp. The following
Main() method 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:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");
// Get a StreamWriter and write string data.
using(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);
}
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE678
8849CH20.qxd 10/17/07 5:58 PM Page 678
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Console.WriteLine("Created file and wrote some thoughts ");
Console.ReadLine();
}
Once you run this program, you can examine the contents of this new file (see Figure 20-7). You
will find this file under the bin\Debug folder of your current application, given that you have not
specified an absolute path at the time you called
CreateText().
Figure 20-7. 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 correspon-
ding
StreamReader type. As you recall, this class derives from the abstract TextReader, which offers
the functionality described in Table 20-9.
Table 20-9. 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:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");

// Now read data from file.
Console.WriteLine("Here are your thoughts:\n");
using(StreamReader sr = File.OpenText("reminders.txt"))
{
string input = null;
while ((input = sr.ReadLine()) != null)
{
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 679
8849CH20.qxd 10/17/07 5:58 PM Page 679
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Console.WriteLine (input);
}
}
Console.ReadLine();
}

Once you run the program, you will see the character data within reminders.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 follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with StreamWriter / StreamReader *****\n");
// Get a StreamWriter and write string data.
using(StreamWriter writer = new StreamWriter("reminders.txt"))
{

}
// Now read data from file.
using(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 20 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 infor-
mation to an underlying buffer. To illustrate, the following Console Application (named
StringReaderWriterApp) writes a block of string data to a
StringWriter object rather than a
file on the local hard drive:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with StringWriter / StringReader *****\n");
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE680
8849CH20.qxd 10/17/07 5:58 PM Page 680
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
// Create a StringWriter and emit character data to memory.
using(StringWriter strWriter = new StringWriter())
{
strWriter.WriteLine("Don't forget Mother's Day this year ");
// Get a copy of the contents (stored in a string) and pump
// to console.
Console.WriteLine("Contents of StringWriter:\n{0}", strWriter);
}
Console.ReadLine();
}
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:
using (StringWriter strWriter = new StringWriter())
{
strWriter.WriteLine("Don't forget Mother's Day this year ");
Console.WriteLine("Contents of StringWriter:\n{0}", strWriter);
// 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:
using (StringWriter strWriter = new StringWriter())
{
strWriter.WriteLine("Don't forget Mother's Day this year ");
Console.WriteLine("Contents of StringWriter:\n{0}", strWriter);
// Read data from the StringWriter.
using (StringReader strReader = new StringReader(strWriter.ToString()))
{
string input = null;
while ((input = strReader.ReadLine()) != null)
{
Console.WriteLine(input);
}
}
}

■Source Code The StringReaderWriterApp is included under the Chapter 20 subdirectory.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 681
8849CH20.qxd 10/17/07 5:58 PM Page 681
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
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 20-10).
Table 20-10. 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 mem-
bers described in Table 20-11.
Table 20-11. 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 read methods that grab the next type
from the stream (ReadBoolean(), ReadByte(), ReadInt32(), and so forth).
The following example (a Console Application named BinaryWriterReader) writes a number of
data types to a new
*.dat file:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Binary Writers / Readers *****\n");
// Open a binary writer for a file.
FileInfo f = new FileInfo("BinFile.dat");
using(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);
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE682
8849CH20.qxd 10/17/07 5:58 PM Page 682
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
// Create some data to save in the file
double aDouble = 1234.67;
int anInt = 34567;
string aString = "A, B, C";
// Write the data
bw.Write(aDouble);

bw.Write(anInt);
bw.Write(aString);
}
Console.ReadLine();
}
Notice how the FileStream object returned from FileInfo.OpenWrite() is passed to the con-
structor 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 call various read-centric members to pluck each chunk of data from the
file stream:
static void Main(string[] args)
{

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

// Read the binary data from the stream.
using(BinaryReader br = new BinaryReader(f.OpenRead()))
{
Console.WriteLine(br.ReadDouble());
Console.WriteLine(br.ReadInt32());
Console.WriteLine(br.ReadString());
}

Console.ReadLine();
}
■Source Code The BinaryWriterReader application is included under the Chapter 20 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 programmati-
cally 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
System.IO.NotifyFilters enumer-
ation (while many of these members are self-explanatory, check the .NET Framework 3.5 SDK
documentation for further details):
public enum NotifyFilters
{
Attributes, CreationTime,
DirectoryName, FileName,
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 683
8849CH20.qxd 10/17/07 5:58 PM Page 683
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
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 follow-
ing Console Application (named MyDirectoryWatcher) will monitor the
*.txt files within the
MyFolder directory 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;
}
// 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);
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE684
8849CH20.qxd 10/17/07 5:58 PM Page 684
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
// 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, and so forth. You will see various bits of information
regarding the state of the text files within MyFolder (see Figure 20-8).
Figure 20-8. Watching some text files
■Source Code The MyDirectoryWatcher application is included under the Chapter 20 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 18). Because large file I/O oper-
ations 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 conjunc-
tion with the
IAsyncResult type:
public abstract class Stream :
MarshalByRefObject, IDisposable
{
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 685
8849CH20.qxd 10/17/07 5:58 PM Page 685
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -


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
Console Application (AsyncFileStream) illustrates one manner in which you can asynchronously
interact with a
FileStream type (be sure to import the System.Threading and System.IO name-
spaces):
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Async File I/O *****\n");
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);
}
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 del-
egates!) 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.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE686
8849CH20.qxd 10/17/07 5:58 PM Page 686
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
■Source Code The AsyncFileStream application is included under the Chapter 20 subdirectory.
Understanding the Role of Isolated Storage

Each of the file I/O examples you have just examined make a very big assumption regarding the
execution of the application: that it has been granted
full trust security privileges by the CLR. As you
would imagine, the act of reading from, or writing to, a machine’s hard drive could be a potential
security threat, based on the origin of the application. Recall that the .NET platform supports the
ability to download assemblies from a variety of locations including external websites, from an
intranet, or even dynamically in memory using an assembly created via the types of the
System.
Reflection.Emit
namespace (examined in Chapter 19).
Furthermore, you may also recall from Chapter 15 that the
<codeBase> element of a client
*.config file allows you to declaratively specify such an arbitrary path for the CLR to find an
external assembly; while the
Assembly.LoadFrom() method (see Chapter 16) allows you to program-
matically load assemblies located at an external URI.
It’s a Matter of Trust
Given the fact that a .NET assembly can be loaded from a variety of locations beyond the current
machine’s local hard drive, the issue of trust becomes very important. By way of illustration, assume
you have an application that executes code downloaded from a remote location. If this code library
attempts to read files on the local computer, how can we ensure the library is not attempting to read
sensitive information? Likewise, if we download a remote executable to run on a local machine, how
can we make sure this assembly does not attempt to read sensitive data within the system registry,
make calls to the underlying API of the operating system (for evil purposes), or other such potential
security risks?
The answer, as far as the .NET platform is concerned, is to make use of a .NET-centric security
mechanism known as Code Access Security. Using CAS, the CLR can deny or grant a number of
security privileges to the executing assembly, including the following:
• Manipulation of a machine’s directory/file structure
• Manipulation of network/web/database connections

• Creation of new application domains/dynamic assemblies
• Use of .NET reflection services
• Calls to unmanaged code using PInvoke
Focusing on the I/O-specific security concerns, assume that you are authoring an application
that will be deployed to external users via some remote web-based URL. Based on how you config-
ure the deployment script (and based on the security policy of the user’s machine), it may run
under a restricted security environment that will
prevent access to the local file system. If your
application must store user or application settings using the types of the
System.IO namespace,
the CLR will throw runtime security exceptions!
The types within the
System.IO.IsolatedStorage namespace can be used to create an applica-
tion that reads and writes data to a very specific location of a .NET-aware machine using
isolated
storage
. This can be understood as a safe “sandbox” where the CLR will allow file I/O operations to
occur, even if the application has been downloaded from an external URL or has in other ways been
placed in a security sandbox by a system administrator.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 687
8849CH20.qxd 10/17/07 5:58 PM Page 687
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Other Uses of the Isolated Storage API
Understand, however, that the isolated storage API is not limited to controlling read/write file oper-
ations on remotely downloaded code. This API also provides a simple way to persist per-user data in
a manner that ensures other applications cannot indirectly (or directly) tamper with said data. For
example, using isolated storage, it is possible to build a single application that saves data in isolated
folders for each user logged on to a specific workstation.
Another benefit of using the isolated storage API is that your code base does not need to hard-

code paths or directory names in the application. Rather, when using isolated storage, an appli-
cation indirectly saves data to a unique data compartment that is associated with some aspect of
the code’s identity, such as its URL, strong name, or X509 digital signature (more information on
code identity later in this chapter).
Thankfully, programming with the isolated storage API is very simple to those who understand
basic file I/O operations. However, before we examine how to do so, allow me to provide an
overview of the .NET Code Access Security model.
■Note Complete coverage of CAS would easily entail an entire chapter (or two). Here I will explain the core
operation of CAS in order to set the foundation for the role of the isolated storage API. Please consult the .NET
Framework 3.5 SDK for further details of CAS if you are so inclined.
A Primer on Code Access Security
To address the security issues involved with downloading and executing remote .NET assemblies,
the CLR will automatically determine the assembly’s identity and assign it to one of many precon-
figured code groups. Simply put, a
code group is a collection of assemblies that all meet the same
criteria (such as the point of origin).
The criterion used by the CLR to determine which code group an assembly belongs to is
referred to as
evidence. Beyond the point of origin, an assembly can be placed into a code group
using other forms of evidence such as an assembly’s strong name, an embedded X509 digital certifi-
cate, or some sort of custom criteria you have accounted for programmatically.
■Note Strictly speaking, evidence comes in two flavors: assembly evidence and host evidence. While assembly
evidence is compiled into the assembly, host evidence can only be specified programmatically using the
AppDomain type.
Once an assembly’s evidence has been evaluated to determine which code group it belongs to,
the CLR will then consult the
permission set (which, as you might guess, is simply a named collec-
tion of individual permissions) associated with the code group to determine what the assembly can
and, more importantly,
cannot do.

Collectively, code groups and their related permissions constitute a
security policy, which can
actually be partitioned at three major levels (enterprise, machine, and user). Using this stratified
approach, it is possible for a system administrator to create unique policies for the company at
large as well as at the machine/user level.
Once each of the security policies have been applied (enterprise, machine, and user), the
assembly will execute under the .NET runtime. If the assembly attempts to execute code outside
of its permission set, the CLR will throw a runtime security exception. Figure 20-9 illustrates how
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE688
8849CH20.qxd 10/17/07 5:58 PM Page 688
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
these building blocks of CAS (evidence, code groups/permission sets, and policies) intertwine from
a high level.
Figure 20-9. The building blocks of CAS
The act of evaluating evidence, placing assemblies into code groups, and mapping permission
sets to the assembly in question happens transparently in the background whenever you run a .NET
application. For the most part, the default CAS security settings and CLR/CAS interactions can be
allowed to function in the background without any direct interaction on your part. However, it is
worth your while to dig a bit deeper into the building blocks of CAS, beginning with the notion of
assembly evidence.
The Role of Evidence
In order for the CLR to determine which code group to place an assembly into, the first step is to
read the supplied evidence. As mentioned, evidence is simply information obtained from an assem-
bly (or possible the hosting application domain) at the point it is loaded into memory. Table 20-12
documents the major types of evidence an assembly can present to the CLR.
Table 20-12.Various Types of Assembly Evidence
Host Evidence Type Meaning in Life
Application directory The installation directory of the assembly
Assembly hash code The hash value of an assembly’s contents

Publisher certificate The Authenticode X509 digital certificate assigned to the assembly (if
any)
Site The source website where an assembly was loaded (does not apply to
assemblies loaded from the local machine)
Assembly strong name The strong name of an assembly (if any)
URL The URL from which an assembly was loaded (HTTP, FTP, file, and so on)
Zone The name of the zone where the assembly was loaded
While the reading of evidence happens automatically, it is possible to programmatically read
evidence as well using the reflection APIs and the
Evidence type within the System.Security.Policy
namespace. To deepen your understanding of evidence, create a new Console Application named
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 689
8849CH20.qxd 10/17/07 5:58 PM Page 689
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
MyEvidenceViewer. Once you have done so, be sure to import the System.Reflection, System.
Collections
, and System.Security.Policy namespaces.
We will now build a simple application that will prompt the user for the name of an assembly
to load into memory. At this point, we will enumerate over each supplied form of assembly evidence
and print the data to the console window. To begin, the
Program type provides a Main() method that
allows users to enter the full path to the assembly they wish to evaluate. If they enter the
L option,
we will call a helper method that attempts to load the specified assembly into memory. If success-
ful, we will pass the
Assembly reference to another helper method named DisplayAsmEvidence().
Here is the story so far:
class Program
{

static void Main(string[] args)
{
bool isUserDone = false;
string userOption = "";
Assembly asm = null;
Console.WriteLine("***** Evidence Viewer *****\n");
do
{
Console.Write("L (load assembly) or Q (quit): ");
userOption = Console.ReadLine();
switch (userOption.ToLower())
{
case "l":
asm = LoadAsm();
if (asm != null)
{
DisplayAsmEvidence(asm);
}
break;
case "q":
isUserDone = true;
break;
default:
Console.WriteLine("I have no idea what you want!");
break;
}
} while (!isUserDone);
}
}
The LoadAsm() method will simply call Assembly.LoadFrom() to set the private Assembly

member variable:
private static Assembly LoadAsm()
{
Console.Write("Enter path to assembly: ");
try
{
return Assembly.LoadFrom(Console.ReadLine());
}
catch
{
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE690
8849CH20.qxd 10/17/07 5:58 PM Page 690
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Console.WriteLine("Load error ");
return null;
}
}
Finally, the DisplayAsmEvidence() method will extract out the evidence of the loaded assembly
via the
Evidence property of the Assembly type. From here, we obtain an enumerator (via the
GetHostEvidence() method of the Evidence type) and print out each flavor of presented evidence:
private static void DisplayAsmEvidence(Assembly asm)
{
// Get evidence collection and underlying enumerator.
Evidence e = asm.Evidence;
IEnumerator itfEnum = e.GetHostEnumerator();
// Now print out the evidence.
while (itfEnum.MoveNext())
{

Console.WriteLine(" **** Press Enter to continue ****");
Console.ReadLine();
Console.WriteLine(itfEnum.Current);
}
}
To test our application, my suggestion is to create a folder directly off your C drive named
MyAsms. Into this folder, copy the strongly named
CarLibrary.dll assembly (from Chapter 15), and
run your program. Assuming you opt for the
L command, specify the full path to your assembly and
press Enter. Your application should now print out each flavor of evidence to the console, as shown
in Figure 20-10 (note that the way our application was created, you will need to hit the Enter key to
display each form of evidence).
Figure 20-10. Viewing the evidence of CarLibrary.dll
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 691
8849CH20.qxd 10/17/07 5:58 PM Page 691
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Here you can see that CarLibrary.dll has been placed into the MyComputer zone, from the
URL of
C:\MyAsms\CarLibrary.dll, and has a specific strong name value. If you were to load assem-
blies from an entirely different location (such as a remote website), you would obviously see unique
output. In any case, at this point simply understand that when an assembly is loaded into memory,
the CLR will investigate the supplied evidence.
■Source Code The MyEvidenceViewer project is included under the Chapter 20 subdirectory.
The Role of Code Groups
Using evidence, the CLR can then place the assembly into a code group. Each code group is
mapped to a security zone that has a default out-of-the-box set of security settings the CLR will use
to dictate exactly what the assembly can (or cannot) do. Table 20-13 documents some of the more
common code groups to be aware of, including their default assigned permission set (examined in

the next section).
Table 20-13. Several Common Code Groups
Default Code Group Assigned Permission Set Meaning in Life
My_Computer_Zone Full Trust Represents an assembly loaded directly from
the local hard drive
LocalIntranet_Zone LocalIntranet Represents an assembly downloaded from a
share point on the local intranet
Internet_Zone Internet Represents an assembly downloaded from
the World Wide Web
The .NET Framework 3.5 SDK ships with a GUI administration tool that allows system adminis-
trators to view and tweak existing code groups or, if need be, define a brand new code group. For
example, using this tool it is possible to inform the CLR that any external assembly downloaded
from a particular URL (such as
) should execute within a customized
security sandbox.
■Note The .NET Framework 3.5 SDK also provides a command-line tool named caspol.exe that provides the
same options.
This tool can be run by double-clicking the Microsoft .NET Framework Configuration applet
located under the Administrative Tools folder of your machine’s Control Panel. Once you launch
this tool, you are able to configure CAS settings on three basic levels: the enterprise at large (e.g.,
all networked machines), the current machine, or on a per-user basis. (It is also possible to config-
ure CAS on the application domain level as well, but this can only be done programmatically.)
Figure 20-11 shows the expended nodes of the default “machine-level policy” settings for CAS on
my current computer.
Again, notice that the All_Code code group (which represents all .NET assemblies) defines sev-
eral zones to which an assembly can belong (My_Computer_Zone, etc.). If you were to right-click
any of the nodes under the All_Code root, you would be able to activate a property page that
describes further details. For example, Figure 20-12 shows the properties of the My_Computer_
Zone code group, which again represents any assembly loaded directly from the local hard drive.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE692

8849CH20.qxd 10/17/07 5:58 PM Page 692
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Figure 20-11. The CAS-centric machine policy
Figure 20-12. Details of the My_Computer_Zone code group
If you were to click the Membership Condition tab, you would be able to determine how the
CLR figured out whether a given .NET assembly should be a member of this zone. In Figure 20-13,
you can see the membership condition is the rather nondescript value Zone, meaning the location
at which the assembly has been loaded.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 693
8849CH20.qxd 10/17/07 5:58 PM Page 693
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Figure 20-13. The membership condition for the My_Computer_Zone code group
If you were to click the Permission Set tab of the My_Computer_Zone code group, you would
see that any assembly loaded from the local hard drive is assigned a set of security permissions
given the name Full Trust (see Figure 20-14).
Figure 20-14. Assemblies loaded from the local hard drive are granted Full Trust permissions by
default.
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE694
8849CH20.qxd 10/17/07 5:58 PM Page 694
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
In contrast, the permission set granted to assemblies that are loaded from an external URL
(outside of a company intranet) will be placed into the Internet_Zone code group, which runs under
a much more restrictive set of privileges. As you can see in Figure 20-15, assemblies loaded into the
Internet_Zone group are not given Full Trust permissions, but are rather assigned to a set of permis-
sions named Internet.
Figure 20-15. Assemblies loaded from external URLs are not granted Full Trust permissions by default.
The Role of Permission Sets

As you have seen, the CLR will place an assembly into a code group based on various criteria (origin
of the load, strong name, etc.). The code group in turn has a set of permissions assigned to it that
are given a friendly name such as Full Trust, Internet, and so forth. Each permission set (as the
name implies) is a collection of individually configured permissions that control various security
settings (access to the printer, access to the file system, use of the reflection API, etc.). Using the
Microsoft .NET Framework Configuration utility, you are able to view the settings for any of the
default permission sets simply by selecting the View Permissions link. Figure 20-16 shows the
permissions for the Internet permission set, once the View Permissions link has been clicked.
Each of these icons (File Dialog, Isolated Storage File, Security, etc.) represents a specific per-
mission in the set, all of which can be further configured by double-clicking a given item. For
example, if you were to double-click the Security permission (which is sort of a catchall permission
for common security settings), you could see that if an assembly is running under the Internet_
Zone (thereby restricted by the Internet permission set), it is able to execute, but by default
cannot
perform a number of other basic details, such as make use of platform invocation services to call
into the API of the operating system (see Figure 20-17).
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE 695
8849CH20.qxd 10/17/07 5:58 PM Page 695
www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -
Figure 20-16. Internet permission set
Figure 20-17. Viewing individual permissions for a permisson set
Observing CAS in Action
To illustrate a very simple example of CAS operations, we will change some default settings for the
My_Computer code group and observe the results. Begin by right-clicking the My_Computer_Zone
code group for your machine policy using the Microsoft .NET Framework Configuration applet to
open the related property page. Next, click the Permission Set tab, and change the permission set
from Full Trust to Internet (see Figure 20-18).
CHAPTER 20 ■ FILE I/O AND ISOLATED STORAGE696
8849CH20.qxd 10/17/07 5:58 PM Page 696

www.free-ebooks-download.org
Simpo PDF Merge and Split Unregistered Version -

×