[ Team LiB ] Previous Section Next Section

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);



  }
}
    [ Team LiB ] Previous Section Next Section