For
many applications, data is held in memory and accessed as if it were
a three-dimensional solid; when you need to access a variable or an
object, you use its name—and, presto, it is available to you.
When you want to move your data into or out of a file, or across the
network or over the Internet, however, your data must be
streamed
. In a stream
packets of data flow one after the other, much like bubbles in a
stream of water.
The endpoint of a stream is a backing store. The backing store provides a source for the stream, like a lake provides a source for a river. Typically, the backing store will be a file, but it is also possible for the backing store to be a network or web connection.
Files and directories are abstracted by classes in the .NET Framework. These classes provide methods and properties for creating, naming, manipulating, and deleting files and directories on your disk.
The .NET Framework provides both buffered and unbuffered streams, and provides classes for asynchronous I/O as well. With asynchronous I/O you can instruct the .NET classes to read your file, and while they are busy getting the bits off the disk your program can be working on other tasks. The asynchronous I/O tasks notify you when their work is done. The asynchronous classes are sufficiently powerful and robust that you might be able to avoid creating threads explicitly (see Chapter 20).
Streaming into and out of files is no different than streaming across the network, and the second part of this chapter will describe streaming using both TCP/IP and web protocols.
To create a stream of data, your object must be
serialized
, or written to the
stream as a series of bits. You have already encountered
serialization in Chapter 19
.
The .NET Frameworks provide extensive support for
serialization, and the final part of this chapter will walk you
through the details of taking control of the serialization of your
object.
Before looking at how you can get data into and out of files, let’s start by examining the support provided for file and directory manipulation.
The classes you need are in the System.IO
namespace. These include the File
class, which
represents a file on disk, and the Directory
class, which represents a directory (known in Windows as a
folder
).
The Directory
class exposes static methods for creating,
moving, and exploring directories. All the methods of the
Directory
class are static, and therefore you can
call them all without having an instance of the class.
The DirectoryInfo
class is a similar class but one
which has nothing but instance members (i.e., no static members at
all). DirectoryInfo
derives from
FileSystemInfo
, which in turn derives from
MarshalByRefObject
. The
FileSystemInfo
class has a number of properties
and methods which provide information about a file or directory.
Table 21-1 lists the principal methods of the
Directory
class, and Table 21-2
lists the principal methods of the DirectoryInfo
class including important properties and methods inherited from
FileSystemInfo
.
Table 21-1. Principal methods of the Directory class
Method |
Use |
---|---|
Creates all directories and subdirectories specified by its path parameter. | |
Deletes the directory and deletes all its contents. | |
Returns a Boolean value, | |
Returns the creation date and time of the directory; sets the creation date and time. | |
Returns the current directory; sets the current directory. | |
Gets an array of subdirectories. | |
Returns the root of the specified path. | |
Returns an array of strings with the filenames for the files in the specified directory. | |
Returns the last time the specified directory was accessed; sets the last time the specified directory was accessed. | |
Returns the last time the specified directory was written to; sets the last time the specified directory was written to. | |
Returns the names of all the logical drives in the form
| |
Returns the parent directory for the specified path. | |
Moves a directory and its contents to a specified path. |
Table 21-2. Principal methods and properties of the DirectoryInfo class
Method or Property |
Use |
---|---|
Inherits from | |
Inherits from | |
|
Public property Boolean value, |
Public property inherited from | |
Public property inherited from | |
Public property inherited from | |
Public property inherited from | |
Public property name of this instance of
| |
Public property parent directory of the specified directory. | |
Public property root portion of the path. | |
Public method that creates a directory. | |
Public method that creates a subdirectory on the specified path. | |
|
Public method that deletes a |
Public method that returns a | |
Public method that returns a list of files in the directory. | |
Public method that retrieves an array of
| |
Public method that moves a | |
Public method inherited from |
To
explore a directory hierarchy, you need
to instantiate a DirectoryInfo
object. The
DirectoryInfo
class provides methods for getting
not just the names of contained files and directories, but also
FileInfo
and DirectoryInfo
objects, allowing you to dive into the hierarchical structure,
extracting subdirectories and exploring these recursively.
You instantiate a DirectoryInfo
object with the
name of the directory you want to explore:
DirectoryInfo dir = new DirectoryInfo(@"C:winNT");
Remember that the @ sign before a string creates a verbatim string literal in which it is not necessary to escape characters such as the backslash. This is covered in Chapter 10.
You can ask that DirectoryInfo
object for
information about itself, including its name, full path, attributes,
the time it was last accessed, and so forth. To explore the
subdirectory hierarchy, you ask the current directory for its list of
subdirectories.
DirectoryInfo[] directories = dir.GetDirectories( );
This returns an array of DirectoryInfo
objects,
each of which represents a directory. You can then recurse into the
same method, passing in each DirectoryInfo
object
in turn:
foreach (DirectoryInfo newDir in directories) { dirCounter++; ExploreDirectory(newDir); }
The dirCounter
static int
member variable keeps track of how many subdirectories have been
found altogether. To make the display more interesting, you’ll
add a second static int
member variable
indentLevel
that will be incremented each time you
recurse into a subdirectory, and decremented when you pop out. This
will allow you to display the subdirectories indented under the
parent directories. The complete listing is shown in Example 21-1.
Example 21-1. Recursing through subdirectories
namespace Programming_CSharp { using System; using System.IO; class Tester { public static void Main( ) { Tester t = new Tester( ); // choose the initial subdirectory string theDirectory = @"c:WinNT"; // call the method to explore the directory, // displaying its access date and all // subdirectoriesDirectoryInfo dir = new DirectoryInfo(theDirectory);
t.ExploreDirectory(dir);
// completed. print the statistics Console.WriteLine( " {0} directories found. ", dirCounter); } // Set it running with a directoryInfo object // for each directory it finds, it will call // itself recursivelyprivate void ExploreDirectory(DirectoryInfo dir)
{
indentLevel++; // push a directory level
// create indentation for subdirectories
for (int i = 0; i < indentLevel; i++)
Console.Write(" "); // two spaces per level
// print the directory and the time last accessed
Console.WriteLine("[{0}] {1} [{2}] ",
indentLevel, dir.Name, dir.LastAccessTime);
// get all the directories in the current directory
// and call this method recursively on each
DirectoryInfo[] directories = dir.GetDirectories( );
foreach (DirectoryInfo newDir in directories)
{
dirCounter++; // increment the counter
ExploreDirectory(newDir);
}
indentLevel--; // pop a directory level
}
// static member variables to keep track of totals // and indentation level static int dirCounter = 1; static int indentLevel = -1; // so first push = 0 } } Output (excerpt): [2] logiscan [5/1/2001 3:06:41 PM] [2] miitwain [5/1/2001 3:06:41 PM] [1] Web [5/1/2001 3:06:41 PM] [2] printers [5/1/2001 3:06:41 PM] [3] images [5/1/2001 3:06:41 PM] [2] Wallpaper [5/1/2001 3:06:41 PM] 363 directories found.
The program begins by identifying a directory (WinNT
) and creating a DirectoryInfo
object
for that directory. It then calls
ExploreDirectory
, passing in that
DirectoryInfo
object.
ExploreDirectory
displays information about the
directory, and then retrieves all the subdirectories.
The list of all the subdirectories of the current directory is
obtained by calling GetDirectories
. This returns
an array of DirectoryInfo
objects.
ExploreDirectory
is the recursive method; each
DirectoryInfo
object is passed into
ExploreDirectory
in turn. The effect is to push
recursively into each subdirectory, and then pop back out to explore
sister directories until all the subdirectories of
WinNT are displayed. When
ExploreDirectory
finally returns, the calling
method prints a summary.
The
DirectoryInfo
object
can also return a collection of all the files in each subdirectory
found. The GetFiles( )
method returns an array of
FileInfo
objects, each of which describes a file
in that directory. The FileInfo
and
File
objects relate to one another, much as
DirectoryInfo
and Directory
do.
Like the methods of Directory
, all the
File
methods are static; and like
DirectoryInfo
, all the methods of
FileInfo
are instance methods.
Table 21-3 lists the principal methods of the
File
class, and Table 21-4 lists
the important members of the FileInfo
class.
Table 21-3. Principal public static methods of the File class
Table 21-4. Methods and properties of the FileInfo class
Example 21-2 modifies Example 21-1,
adding code to get a FileInfo
object for each file
in each subdirectory. That object is used to display the name of the
file, along with its length and the date and time it was last
accessed.
Example 21-2. Exploring files and subdirectories
namespace Programming_CSharp { using System; using System.IO; class Tester { public static void Main( ) { Tester t = new Tester( ); // choose the initial subdirectory string theDirectory = @"c:WinNT"; // call the method to explore the directory, // displaying its access date and all // subdirectories DirectoryInfo dir = new DirectoryInfo(theDirectory); t.ExploreDirectory(dir); // completed. print the statisticsConsole.WriteLine(
" {0} files in {1} directories found. ",
fileCounter,dirCounter);
} // Set it running with a directoryInfo object // for each directory it finds, it will call // itself recursively private void ExploreDirectory(DirectoryInfo dir) { indentLevel++; // push a directory level // create indentation for subdirectories for (int i = 0; i < indentLevel; i++) Console.Write(" "); // two spaces per level // print the directory and the time last accessed Console.WriteLine("[{0}] {1} [{2}] ", indentLevel, dir.Name, dir.LastAccessTime);// get all the files in the directory and
// print their name, last access time, and size
FileInfo[] filesInDir = dir.GetFiles( );
foreach (FileInfo file in filesInDir)
{
// indent once extra to put files
// under their directory
for (int i = 0; i < indentLevel+1; i++)
Console.Write(" "); // two spaces per level
Console.WriteLine("{0} [{1}] Size: {2} bytes",
file.Name,
file.LastWriteTime,
file.Length);
fileCounter++;
}
// get all the directories in the current directory // and call this method recursively on each DirectoryInfo[] directories = dir.GetDirectories( ); foreach (DirectoryInfo newDir in directories) { dirCounter++; // increment the counter ExploreDirectory(newDir); } indentLevel--; // pop a directory level } // static member variables to keep track of totals // and indentation level static int dirCounter = 1; static int indentLevel = -1; // so first push = 0static int fileCounter = 0;
} } Output (excerpt): [0] WinNT [5/1/2001 3:34:01 PM] Active Setup Log.txt [4/20/2001 10:42:22 AM] Size: 10620 bytes actsetup.log [4/20/2001 12:05:02 PM] Size: 8717 bytes Blue Lace 16.bmp [12/6/1999 4:00:00 PM] Size: 1272 bytes [2] Wallpaper [5/1/2001 3:14:32 PM] Boiling Point.jpg [4/20/2001 8:30:24 AM] Size: 28871 bytes Chateau.jpg [4/20/2001 8:30:24 AM] Size: 70605 bytes Windows 2000.jpg [4/20/2001 8:30:24 AM] Size: 129831 bytes 8590 files in 363 directories found.
The example is initialized with the name of the C:WinNT directory. It prints information about all the files in that directory and then recursively explores all the subdirectories and all their subdirectories (your output might differ). This can take quite a while to run because the WinNT directory tree is rather large (363 subdirectories on my machine, as shown in the output).
As
you can see from Figures 21-3 and 21-4, it is possible to use the
FileInfo
class to create, copy, rename, and delete
files. The next example will create a new subdirectory, copy files
in, rename some, delete others, and then delete the entire directory.
To set up these examples, create a est
directory
and copy the media
directory from WinNT into the
test
directory. Do not work on files in WinNT
directly; when working with system files you want to be
extraordinarily careful.
The first step is to create a DirectoryInfo
object
for the test directory:
string theDirectory = @"c: estmedia"; DirectoryInfo dir = new DirectoryInfo(theDirectory);
Next, create a subdirectory within the test directory by calling
CreateSubDirectory
on the
DirectoryInfo
object. You get back a new
DirectoryInfo
object, representing the newly
created subdirectory:
string newDirectory = "newTest"; DirectoryInfo newSubDir = dir.CreateSubdirectory(newDirectory);
You can now iterate over the test and copy files to the newly created subdirectory:
FileInfo[] filesInDir = dir.GetFiles( ); foreach (FileInfo file in filesInDir) { string fullName = newSubDir.FullName + "\" + file.Name; file.CopyTo(fullName); Console.WriteLine("{0} copied to newTest", file.FullName); }
Notice the syntax of the
CopyTo
method. This is a method of the FileInfo
object. You pass in the full path of the new file, including its full
name and extension.
Once you’ve copied the files, you can get a list of the files in the new subdirectory and work with them directly:
filesInDir = newSubDir.GetFiles( ); foreach (FileInfo file in filesInDir) {
Create a simple integer variable named counter
and
use it to rename every other file:
if (counter++ %2 == 0) { file.MoveTo(fullName + ".bak"); Console.WriteLine("{0} renamed to {1}", fullName,file.FullName); }
You rename a file by “moving” it to the same directory but with a new name. You can, of course, move a file to a new directory with its original name, or you can move and rename at the same time.
You’ll rename every other file, and you’ll delete the ones you don’t rename:
file.Delete( ); Console.WriteLine("{0} deleted.", fullName);
Once you’re done manipulating the files, you can clean up by deleting the entire subdirectory:
newSubDir.Delete(true);
The Boolean parameter determines whether this is a recursive delete.
If you pass in false
, and if this directory has
subdirectories with files in it, it will throw an exception.
Example 21-3 lists the source code for the complete program. Be careful when running this; when it is done, the subdirectory is gone. To see the renaming and deletions, either put a breakpoint on the last line or remove the last line.
Example 21-3. Creating a subdirectory and manipulating files
newSubDir.Delete(true);
namespace Programming_CSharp
{
using System;
using System.IO;
class Tester
{
public static void Main( )
{
// make an instance and run it
Tester t = new Tester( );
string theDirectory = @"c: estmedia";
DirectoryInfo dir = new DirectoryInfo(theDirectory);
t.ExploreDirectory(dir);
}
// Set it running with a directory name
private void ExploreDirectory(DirectoryInfo dir)
{
// make a new subdirectory
string newDirectory = "newTest";
DirectoryInfo newSubDir =
dir.CreateSubdirectory(newDirectory);
// get all the files in the directory and
// copy them to the new directory
FileInfo[] filesInDir = dir.GetFiles( );
foreach (FileInfo file in filesInDir)
{
string fullName = newSubDir.FullName +
"\" + file.Name;
file.CopyTo(fullName);
Console.WriteLine("{0} copied to newTest",
file.FullName);
}
// get a collection of the files copied in
filesInDir = newSubDir.GetFiles( );
// delete some and rename others
int counter = 0;
foreach (FileInfo file in filesInDir)
{
string fullName = file.FullName;
if (counter++ %2 == 0)
{
file.MoveTo(fullName + ".bak");
Console.WriteLine("{0} renamed to {1}",
fullName,file.FullName);
}
else
{
file.Delete( );
Console.WriteLine("{0} deleted.",
fullName);
}
}
newSubDir.Delete(true); // delete the subdirectory
}
}
}
Output (excerpts):
c: estmediaBach's Brandenburg Concerto No. 3.RMI
copied to newTest
c: estmediaBeethoven's 5th Symphony.RMI copied to newTest
c: estmediaBeethoven's Fur Elise.RMI copied to newTest
c: estmediacanyon.mid copied to newTest
c: estmedia
ewTestBach's Brandenburg Concerto
No. 3.RMI renamed to
c: estmedia
ewTestBach's Brandenburg Concerto
No. 3.RMI.bak
c: estmedia
ewTestBeethoven's 5th Symphony.RMI deleted.
c: estmedia
ewTestBeethoven's Fur Elise.RMI renamed to
c: estmedia
ewTestBeethoven's Fur Elise.RMI.bak
c: estmedia
ewTestcanyon.mid deleted.
52.14.172.93