10.3 Directories and Files
The File and Directory classes
contain
static methods and properties that encapsulate the operations
typically associated with file I/O, such as copying, moving,
deleting, renaming, and enumerating files and directories.
The actual manipulation of the contents of a file is done with a
FileStream. The File class has
methods that return a FileStream, though you may
directly instantiate a FileStream.
10.3.1 Reading and Writing Files
This example reads in
and prints out the first line of a text
file specified on the command line:
using System;
using System.IO;
class FileDumper {
static void Main(string[ ] args) {
Stream s = File.OpenRead(args[0]);
StreamReader sr = new StreamReader(s);
Console.WriteLine(sr.ReadLine( ));
sr.Close( );
}
}
10.3.2 Examining Directories
To examine the contents
of the filesystem, use the
DirectoryInfo and FileInfo
classes, both of which are derived from a common
FileSystemInfo base class. These provide access to
most of the filesystem information, which the following example
demonstrates by replicating the results of the dir
command:
using System;
using System.IO;
using System.Text;
class DirCommand {
static void Main( ) {
long numFiles=0, numDirs=0, totalBytes=0;
string currentDir = Directory.GetCurrentDirectory( );
DirectoryInfo currentDirInfo = new DirectoryInfo(currentDir);
StringBuilder sb = new StringBuilder( );
sb.AppendFormat(" Directory of {0}\n\n", currentDirInfo.FullName);
DirectoryInfo rootDirInfo = currentDirInfo.Root;
if (rootDirInfo != null) {
sb.AppendFormat("{0:dd/MM/yyyy hh:mm tt} <DIR> .\n",
rootDirInfo.LastWriteTime);
numDirs++;
}
DirectoryInfo parentDirInfo = currentDirInfo.Parent;
if (parentDirInfo != null) {
sb.AppendFormat("{0:dd/MM/yyyy hh:mm tt} <DIR> ..\n",
parentDirInfo.LastWriteTime);
numDirs++;
}
FileSystemInfo[ ] fsis = currentDirInfo.GetFileSystemInfos( );
foreach (FileSystemInfo fsi in fsis) {
FileInfo fi = fsi as FileInfo;
if (fi != null) {
sb.AppendFormat("{0:dd/MM/yyyy hh:mm tt} {1,14:N0} {2}\n",
fi.LastWriteTime, fi.Length, fi.Name);
numFiles++;
totalBytes += fi.Length;
}
DirectoryInfo di = fsi as DirectoryInfo;
if (di != null) {
sb.AppendFormat("{0:dd/MM/yyyy hh:mm tt} <DIR> {1}\n",
di.LastWriteTime, di.Name);
numDirs++;
}
}
sb.AppendFormat("{0,16:G} File(s) {1,14:N0} bytes\n", numFiles,
totalBytes);
sb.AppendFormat("{0,16:G} Dir(s)\n", numDirs);
Console.WriteLine(sb.ToString( ));
}
}
10.3.3 Catching Filesystem Events
To monitor a filesystem
for changes (creation,
modification, or deletion of a file, for example), use a
FileSystemWatcher with the
FileSystemEventHandler,
RenamedEventHandler, and
ErrorEventHandler delegates.
ErrorEventHandler does not inform you of
filesystem errors. Instead, it indicates that the
FileSystemWatcher's event buffer
overflowed because it was overwhelmed by Changed,
Created, Deleted, or
Renamed events.
The following example monitors whatever directory you specify on the
command line. Run it using the name of an existing directory, and
then rename, delete, create, and modify some files in that directory:
// WatchFS.cs - use WatchFS.exe <path> to monitor file system
using System;
using System.IO;
using System.Threading;
class WatchFS {
static void FSErrorCallback(object o, ErrorEventArgs eea) {
Console.WriteLine("Error: {0}", eea.GetException( ).Message);
}
static void FSRenamedCallback(object o, RenamedEventArgs rea) {
Console.WriteLine("Renamed: {0}->{1}", rea.OldFullPath, rea.FullPath);
}
static void FSEventCallback(object o, FileSystemEventArgs fsea) {
Console.WriteLine("Event: {0}->{1}", fsea.ChangeType, fsea.FullPath);
}
static void Main(string[ ] args) {
// Register handlers for file system events
FileSystemWatcher fsw = new FileSystemWatcher(args[0]);
fsw.Changed += new FileSystemEventHandler(FSEventCallback);
fsw.Created += new FileSystemEventHandler(FSEventCallback);
fsw.Deleted += new FileSystemEventHandler(FSEventCallback);
fsw.Renamed += new RenamedEventHandler(FSRenamedCallback);
fsw.Error += new ErrorEventHandler(FSErrorCallback);
fsw.EnableRaisingEvents = true;
Console.WriteLine("Listening for events - press <enter> to end");
Console.ReadLine( ); // Wait for keypress to end
}
}
10.3.4 Asynchronous I/O
I/O doesn't always happen on
your
terms梐ll too often, it takes a lot more time than you would
like. Since I/O tasks are not necessarily CPU-bound, your code might
be free to do some other things while a large file transfer runs in
the background. For example, your CPU could be busy handling user
input, rather than hanging while your user saves a 10 MB file across
a VPN connection that's riding on top of a 56K
dialup session.
The .NET Framework provides an asynchronous I/O for these cases. The
first thing to do is create a class to maintain state between
asynchronous calls. The following example uses the
ReadState class to maintain state. It contains a
buffer, an input stream, an AsyncCallback,
and a
StringBuilder. The first three are needed only
during the asynchronous calls. The last, which accumulates the
contents of the stream during each call, is also used after all the
calls have completed.
The AsyncCallback delegate is defined in
IOReadCallback. It's responsible
for pausing the asynchronous operation using Stream.EndRead(
), appending the contents of the buffer to the
StringBuilder, and restarting the operation with
Stream.BeginRead( ).
Inside the delegate, the arguments to BeginRead( )
are identical to those used in the first call to BeginRead(
), which kicks off the asynchronous operation in the
Main( ) method. Each call to BeginRead(
) returns an IAsyncResult object, which
is a token that represents the asynchronous operation. While the
asynchronous read is proceeding, the Main( )
method uses System.Net.WebClient to download a
file. When it's done, it waits on the
IAsyncResult's
WaitHandle until the entire file has been read.
Then, it prints out the contents of the file.
// AsyncRead.cs - use AsyncRead.exe <filename> to test
using System;
using System.IO;
using System.Text;
class AsyncReadFun {
class AsyncReadState {
public Stream stm; // Underlying stream
public byte[ ] buf = new byte[256]; // Read buffer
public StringBuilder sb = new StringBuilder( ); // Result buffer
public AsyncCallback acb = new AsyncCallback(AsyncReadCallback);
}
static void AsyncReadCallback(IAsyncResult iar) {
AsyncReadState ars = (AsyncReadState)iar.AsyncState;
int bytes = ars.stm.EndRead(iar); // Get count of bytes read
if (bytes > 0) { // Copy read bytes and restart read
ars.sb.Append(Encoding.ASCII.GetString(ars.buf, 0, bytes));
Console.WriteLine("Read chunk of {0} bytes", bytes);
ars.stm.BeginRead(ars.buf, 0, ars.buf.Length, ars.acb, ars);
}
}
static void Main(string[ ] args) {
// Open the stream & start reading
Stream s = File.OpenRead(args[0]);
AsyncReadState ars = new AsyncReadState( );
ars.stm = s; // Save the stream reference
IAsyncResult iar = s.BeginRead(ars.buf, 0, ars.buf.Length,
ars.acb, ars);
// Download a file while we're reading the stream
System.Net.WebClient wc = new System.Net.WebClient( );
wc.DownloadFile("http://www.oreilly.com", "index.html");
Console.WriteLine("Finished downloading index.html.");
iar.AsyncWaitHandle.WaitOne( ); // Wait until the async read is done
Console.WriteLine(ars.sb);
}
}
|