
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
524C 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
class Program
{
static void Main(string[] args)
{
...
//List all drives on current computer. string[] drives = Directory.GetLogicalDrives(); Console.WriteLine("Here are your drives:"); foreach(string s in drives)
Console.WriteLine("—>{0} ", s);
//Delete what was created.
Console.WriteLine("Press Enter to delete directories"); Console.ReadLine();
try
{
Directory.Delete(@"C:\Windows\MyFoo");
//The second parameter specifies if you
//wish to destroy any subdirectories.
Directory.Delete(@"C:\Windows\MyBar", true);
}
catch(IOException e)
{
Console.WriteLine(e.Message);
}
}
}
■Source Code The MyDirectoryApp project is located under the Chapter 16 subdirectory.
Working with the DriveInfo Class Type
As of .NET 2.0, the System.IO namespace provides a class named DriveInfo. Like Directory. GetLogicalDrives(), the static DriveInfo.GetDrives() method allows you to discover the names of a machine’s drives. Unlike Directory.GetLogicalDrives(), however, DriveInfo provides numerous other details (such as the drive type, available free space, volume label, and whatnot). Consider the following sample code:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with DriveInfo *****\n");
//Get info regarding all drives.
DriveInfo[] myDrives = DriveInfo.GetDrives();
//Now print drive stats.
foreach(DriveInfo d in myDrives)
{
Console.WriteLine("Name: {0}", d.Name); Console.WriteLine("Type: {0}", d.DriveType);

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 |
525 |
// Check to see if the drive is mounted. if (d.IsReady)
{
Console.WriteLine("Free space: {0}", d.TotalFreeSpace); Console.WriteLine("Format: {0}", d.DriveFormat); Console.WriteLine("Label: {0}\n", d.VolumeLabel);
}
}
Console.ReadLine();
}
}
Figure 16-5 shows the output based on my current machine.
Figure 16-5. Gather drive details via DriveInfo
At this point, you have investigated some core behaviors of the Directory, DirectoryInfo, and DriveInfo classes. Next, you’ll learn how to create, open, close, and destroy the files that populate a given directory.
■Source Code The DriveTypeApp project is located under the Chapter 16 subdirectory.
Working with the FileInfo Class
As shown in the MyDirectoryApp example, the FileInfo class allows you to obtain details regarding existing files on your hard drive (time created, size, file attributes, and so forth) and aids in the creation, copying, moving, and destruction of files. In addition to the set of functionality inherited by FileSystemInfo are some core members unique to the FileInfo class, which are described in Table 16-4.

526 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
Table 16-4. FileInfo Core Members
Member |
Meaning in Life |
AppendText() |
Creates a StreamWriter type (described later) that appends text to a file |
CopyTo() |
Copies an existing file to a new file |
Create() |
Creates a new file and returns a FileStream type (described later) to interact |
|
with the newly created file |
CreateText() |
Creates a StreamWriter type that writes a new text file |
Delete() |
Deletes the file to which a FileInfo instance is bound |
Directory |
Gets an instance of the parent directory |
DirectoryName |
Gets the full path to the parent directory |
Length |
Gets the size of the current file or directory |
MoveTo() |
Moves a specified file to a new location, providing the option to specify a new |
|
filename |
Name |
Gets the name of the file |
Open() |
Opens a file with various read/write and sharing privileges |
OpenRead() |
Creates a read-only FileStream |
OpenText() |
Creates a StreamReader type (described later) that reads from an existing text file |
OpenWrite() |
Creates a write-only FileStream type |
|
|
It is important to understand that a majority of the members of the FileInfo class return a specific I/O-centric object (FileStream, StreamWriter, and so forth) that allows you to begin reading and writing data to (or reading from) the associated file in a variety of formats. You will check out these types in just a moment, but until then, let’s examine various ways to obtain a file handle using the FileInfo class type.
The FileInfo.Create() Method
The first way you can create a file handle is to make use of the FileInfo.Create() method:
public class Program
{
static void Main(string[] args)
{
//Make a new file on the C drive.
FileInfo f = new FileInfo(@"C:\Test.dat"); FileStream fs = f.Create();
//Use the FileStream object...
//Close down file stream.
fs.Close();
}
}
Notice that the FileInfo.Create() method returns a FileStream type, which exposes synchronous and asynchronous write/read operations to/from the underlying file. Do know that the FileStream object returned by FileInfo.Create() grants full read/write access to all users.

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 |
527 |
The FileInfo.Open() Method
You can use the FileInfo.Open() method to open existing files as well as create new files with far more precision than FileInfo.Create(). Once the call to Open() completes, you are returned a FileStream object. Ponder the following logic:
static void Main(string[] args)
{
...
//Make a new file via FileInfo.Open().
FileInfo f2 = new FileInfo(@"C:\Test2.dat"); FileStream fs2 = f2.Open( FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.None);
//Use the FileStream object...
//Close down file stream.
fs2.Close();
}
This version of the overloaded Open() method requires three parameters. The first parameter specifies the general flavor of the I/O request (e.g., make a new file, open an existing file, append to a file, etc.), which is specified using the FileMode enumeration:
public enum FileMode
{
//Specifies that the operating system should create a new file.
//If the file already exists, a System.IO.IOException is thrown.
CreateNew,
//Specifies that the operating system should create a new file.
//If the file already exists, it will be overwritten.
Create,
Open,
// Specifies that the operating system should open a file if it exists; otherwise, a new file should be created.
OpenOrCreate,
Truncate, Append
}
The second parameter, a value from the FileAccess enumeration, is used to determine the read/write behavior of the underlying stream:
public enum FileAccess
{
Read,
Write, ReadWrite
}
Finally, you have the third parameter, FileShare, which specifies how the file is to be shared among other file handlers. Here are the core names:
public enum FileShare
{
None,
Read,
Write, ReadWrite
}

528 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
The FileInfo.OpenRead() and FileInfo.OpenWrite() Methods
While the FileInfo.Open() method allows you to obtain a file handle in a very flexible manner, the FileInfo class also provides members named OpenRead() and OpenWrite(). As you might imagine, these methods return a properly configured read-only or write-only FileStream type, without the need to supply various enumeration values. Like FileInfo.Create() and FileInfo.Open(), OpenRead() and OpenWrite() return a FileStream object:
static void Main(string[] args)
{
...
//Get a FileStream object with read-only permissions.
FileInfo f3 = new FileInfo(@"C:\Test3.dat"); FileStream readOnlyStream = f3.OpenRead();
//Use the FileStream object...
readOnlyStream.Close();
//Now get a FileStream object with write-only permissions.
FileInfo f4 = new FileInfo(@"C:\ Test4.dat"); FileStream writeOnlyStream = f4.OpenWrite();
//Use the FileStream object...
writeOnlyStream.Close();
}
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:
static void Main(string[] args)
{
...
// Get a StreamReader object.
FileInfo f5 = new FileInfo(@"C:\boot.ini"); StreamReader sreader = f5.OpenText();
// Use the StreamReader object...
sreader.Close();
}
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:

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 |
529 |
static void Main(string[] args)
{
...
FileInfo f6 = new FileInfo(@"C:\Test5.txt"); StreamWriter swriter = f6.CreateText();
// Use the StreamWriter object...
swriter.Close();
FileInfo f7 = new FileInfo(@"C:\FinalTest.txt");
StreamWriter swriterAppend = f7.AppendText();
// Use the StreamWriter object...
swriterAppend.Close();
}
As you would guess, the StreamWriter type provides a way to write character data to the underlying file.
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 FileStream 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().
FileStream fs = File.Create(@"C:\Test.dat"); fs.Close();
//Obtain FileStream object via File.Open().
FileStream fs2 = File.Open(@"C:\Test2.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
fs2.Close();
//Get a FileStream object with read-only permissions.
FileStream readOnlyStream = File.OpenRead(@"Test3.dat"); readOnlyStream.Close();
//Get a FileStream object with write-only permissions.
FileStream writeOnlyStream = File.OpenWrite(@"Test4.dat"); writeOnlyStream.Close();
//Get a StreamReader object.
StreamReader sreader = File.OpenText(@"C:\boot.ini"); sreader.Close();

530 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
// Get some StreamWriters.
StreamWriter swriter = File.CreateText(@"C:\Test3.txt"); swriter.Close();
StreamWriter swriterAppend = File.AppendText(@"C:\FinalTest.txt"); swriterAppend.Close();
}
New .NET 2.0 File Members
Unlike FileInfo, the File type supports a few unique members (as of .NET 2.0) shown in Table 16-5, which can greatly simplify the processes of reading and writing textual data.
Table 16-5. 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 underlying file handle, for example:
class Program
{
static void Main(string[] args)
{
string[] myTasks = { "Fix bathroom sink", "Call Dave",
"Call Mom and Dad", "Play XBox"};
//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);
}
}
}
Clearly, when you wish to quickly obtain a file handle, the File type will save you some keystrokes. 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:

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 |
531 |
static void Main(string[] args)
{
//Display info about boot.ini and then open
//for read-only access.
FileInfo bootFile = new FileInfo(@"C:\boot.ini"); Console.WriteLine(bootFile.CreationTime); Console.WriteLine(bootFile.LastAccessTime); FileStream readOnlyStream = bootFile.OpenRead(); readOnlyStream.Close();
}
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 written 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. 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 16-6 shows a few descendents of the Stream type.
Figure 16-6. Stream-derived types
■Note Be aware that the concept of a stream is not limited to files or memory locations. To be sure, the .NET libraries provide stream access to networks and other stream-centric abstractions.
Again, Stream descendents represent data as a raw stream of bytes; therefore, working 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 16-6.

532 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
Table 16-6. Abstract Stream Members
Members |
Meaning in Life |
CanRead |
Determine whether the current stream supports reading, seeking, and/or |
CanSeek |
writing. |
CanWrite |
|
Close() |
Closes the current stream and releases any resources (such as sockets and file |
|
handles) associated with the current 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() |
Read a sequence of bytes (or a single byte) from the current stream and advance |
ReadByte() |
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() |
Write a sequence of bytes (or a single byte) to the current stream and advance |
WriteByte() |
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 experiment 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 simple 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 2.0 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 'use' System.Text. static void Main(string[] args)
{
Console.WriteLine("***** Fun with FileStreams *****\n");
// Obtain a FileStream object.
FileStream fStream = File.Open(@"C:\myMessage.dat", FileMode.Create);

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 |
533 |
//Encode a string as an array of bytes. 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));
// Close stream. fStream.Close();
}
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.
Thankfully, the System.IO namespace 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 16 subdirectory.
Working with StreamWriters and StreamReaders
The StreamWriter and StreamReader classes are useful whenever you need to read or write characterbased 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 limited 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. The relationship between each of these new I/O- centric types is shown in Figure 16-7.