2. When the app executes, another compiler (known as the just-in-time compiler
17.4 Classes File and Directory
enables a program to input data from the keyboard.Console.Outrefers to thestandard out- put stream object, which enables a program to output data to the screen.Console.Error
refers to thestandard error stream object, which enables a program to output error messages to the screen. We’ve been usingConsole.OutandConsole.Inin our console apps,Console methodsWriteandWriteLineuseConsole.Outto perform output, andConsolemethods
ReadandReadLineuseConsole.Into perform input.
There are many file-processing classes in the Framework Class Library. The
System.IOnamespaceincludes stream classes such asStreamReader(for text input from a file),StreamWriter(for text output to a file) andFileStream(for both input from and output to a file). These stream classes inherit fromabstractclassesTextReader,Text-
WriterandStream, respectively. Actually, propertiesConsole.InandConsole.Outare of typeTextReaderandTextWriter, respectively. The system creates objects ofTextReader and TextWriter derived classes to initialize Console properties Console.In andCon-
sole.Out.
Abstract classStreamprovides functionality for representing streams as bytes. Classes
FileStream,MemoryStreamandBufferedStream(all from namespaceSystem.IO) inherit from classStream. ClassFileStreamcan be used to write data to and read data from files.
Class MemoryStreamenables the transfer of data directly to and from memory—this is much faster than reading from and writing to external devices. ClassBufferedStreamuses bufferingto transfer data to or from a stream. Buffering is an I/O performance-enhance- ment technique, in which each output operation is directed to a region in memory, called abuffer, that’s large enough to hold the data frommanyoutput operations. Then actual transfer to the output device is performed in one largephysical output operationeach time the buffer fills. The output operations directed to the output buffer in memory often are calledlogical output operations. Buffering can also be used to speed input operations by initially reading more data than is required into a buffer, so subsequent reads get data from high-speed memory rather than a slower external device.
In this chapter, we use key stream classes to implement file-processing programs that create and manipulate sequential-access files.
17.4 Classes File and Directory
Information is stored in files, which are organized in directories (also called folders). Classes
FileandDirectoryenable programs to manipulate files and directories on disk. ClassFile can determine information about files and can be used to open files for reading or writing.
We discuss techniques for writing to and reading from files in subsequent sections.
Figure 17.3 lists several of classFile’sstaticmethods for manipulating and deter- mining information about files. We demonstrate several of these methods in Fig. 17.5.
Fig. 17.2 | C#’s view of ann-byte file.
0 1 2 3 4 5 6 7 8 9 ...
...
n-1
end-of-file marker
ClassDirectoryprovides capabilities for manipulating directories. Figure 17.4 lists some of classDirectory’sstaticmethods for directory manipulation. Figure 17.5 dem- onstrates several of these methods, as well. TheDirectoryInfoobject returned by method
CreateDirectorycontains information about a directory. Much of the information con- tained in classDirectoryInfoalso can be accessed via the methods of classDirectory.
staticMethod Description
AppendText Returns aStreamWriterthat appends text to an existing file or creates a file if one does not exist.
Copy Copies a file to a new file.
Create Creates a file and returns its associatedFileStream.
CreateText Creates a text file and returns its associatedStreamWriter.
Delete Deletes the specified file.
Exists Returnstrueif the specified file exists andfalseotherwise.
GetCreationTime Returns aDateTimeobject representing when the file was created.
GetLastAccessTime Returns aDateTimeobject representing when the file was last accessed.
GetLastWriteTime Returns aDateTimeobject representing when the file was last modified.
Move Moves the specified file to a specified location.
Open Returns aFileStreamassociated with the specified file and equipped with the specified read/write permissions.
OpenRead Returns a read-onlyFileStreamassociated with the specified file.
OpenText Returns aStreamReaderassociated with the specified file.
OpenWrite Returns a writeFileStreamassociated with the specified file.
Fig. 17.3 | Fileclassstaticmethods (partial list).
staticMethod Description
CreateDirectory Creates a directory and returns its associatedDirectoryInfoobject.
Delete Deletes the specified directory.
Exists Returnstrueif the specified directory exists andfalseotherwise.
GetDirectories Returns astringarray containing the names of the subdirectories in the specified directory.
GetFiles Returns astringarray containing the names of the files in the speci- fied directory.
GetCreationTime Returns aDateTimeobject representing when the directory was created.
GetLastAccessTime Returns aDateTimeobject representing when the directory was last accessed.
GetLastWriteTime Returns aDateTimeobject representing when items were last written to the directory.
Move Moves the specified directory to a specified location.
Fig. 17.4 | Directoryclassstaticmethods.
17.4 ClassesFileandDirectory 675
Demonstrating ClassesFileandDirectory
.ClassFileTestForm(Fig. 17.5) usesFileandDirectorymethods to access file and direc- tory information. TheFormcontains theinputTextBox, in which the user enters a file or di- rectory name. For each key that the user presses while typing in theTextBox, the program callsinputTextBox_KeyDown(lines 19–75). If the user presses theEnterkey (line 22), this method displays either the file’s or directory’s contents, depending on the text the user input.
(If the user does not press theEnterkey, this method returns without displaying any con- tent.) Line 28 usesFilemethodExiststo determine whether the user-specified text is the name of an existing file. If so, line 31 invokesprivatemethodGetInformation(lines 79–
97), which callsFilemethodsGetCreationTime(line 88),GetLastWriteTime(line 92) and
GetLastAccessTime(line 96) to access file information. When methodGetInformationre- turns, line 38 instantiates aStreamReaderfor reading text from the file. TheStreamReader
constructor takes as an argument astringcontaining the name and path of the file to open.
Line 40 callsStreamReadermethodReadToEndto read the entire contents of the file as a
string, then appends thestringtooutputTextBox. Once the file has been read, theusing
block terminates and disposes of the corresponding object, which closes the file.
1 // Fig. 17.5: FileTestForm.cs
2 // Using classes File and Directory.
3 using System;
4 using System.Windows.Forms;
5 using System.IO;
6
7 namespace FileTest 8 {
9 // displays contents of files and directories 10 public partial class FileTestForm : Form
11 {
12 // parameterless constructor 13 public FileTestForm()
14 {
15 InitializeComponent();
16 } // end constructor 17
18 // invoked when user presses key
19 private void inputTextBox_KeyDown( object sender, KeyEventArgs e )
20 {
21 // determine whether user pressed Enter key 22 if ( e.KeyCode == Keys.Enter )
23 {
24 // get user-specified file or directory 25 string fileName = inputTextBox.Text;
26
27 // determine whether fileName is a file
28 if ( )
29 {
30 // get file's creation date, modification date, etc.
31 GetInformation( fileName );
32 StreamReader stream = null; // declare StreamReader 33
Fig. 17.5 | Using classesFileandDirectory.(Part 1 of 3.)
File.Exists( fileName )
34 // display file contents through StreamReader
35 try
36 {
37 // obtain reader and file contents
38 using ( stream = new StreamReader( fileName ) )
39 {
40 outputTextBox.AppendText( stream.ReadToEnd() );
41 } // end using
42 } // end try
43 catch ( IOException )
44 {
45 MessageBox.Show( "Error reading from file",
46 "File Error", MessageBoxButtons.OK,
47 MessageBoxIcon.Error );
48 } // end catch
49 } // end if
50 // determine whether fileName is a directory 51 else if ( Directory.Exists( fileName ) )
52 {
53 // get directory's creation date,
54 // modification date, etc.
55 GetInformation( fileName );
56
57 // obtain directory list of specified directory 58
59 60
61 outputTextBox.AppendText( "Directory contents:\n" );
62
63 // output directoryList contents
64 foreach ( var directory in directoryList ) 65 outputTextBox.AppendText( directory + "\n" );
66 } // end else if
67 else
68 {
69 // notify user that neither file nor directory exists 70 MessageBox.Show( inputTextBox.Text +
71 " does not exist", "File Error",
72 MessageBoxButtons.OK, MessageBoxIcon.Error );
73 } // end else
74 } // end if
75 } // end method inputTextBox_KeyDown 76
77 // get information on file or directory, 78 // and output it to outputTextBox
79 private void GetInformation( string fileName )
80 {
81 outputTextBox.Clear();
82
83 // output that file or directory exists
84 outputTextBox.AppendText( fileName + " exists\n" );
85
Fig. 17.5 | Using classesFileandDirectory.(Part 2 of 3.)
string[] directoryList =
Directory.GetDirectories( fileName );
17.4 ClassesFileandDirectory 677
If line 28 determines that the user-specified text is not a file, line 51 determines whether it’s a directory usingDirectory methodExists. If the user specified anexisting directory, line 55 invokes methodGetInformationto access the directory information.
Line 59 callsDirectorymethodGetDirectoriesto obtain astringarray containing the names of the subdirectories in the specified directory. Lines 64–65 display each element in thestringarray. Note that, if line 51 determines that the user-specified text isnota directory name, lines 70–72 notify the user (via aMessageBox) that the name the user entered does not exist as a file or directory.
Searching Directories with LINQ
We now consider another example that uses file- and directory-manipulation capabilities.
Class LINQToFileDirectoryForm (Fig. 17.6) uses LINQ with classes File, Path and 86 // output when file or directory was created
87 outputTextBox.AppendText( "Created: " +
88 + "\n" );
89
90 // output when file or directory was last modified 91 outputTextBox.AppendText( "Last modified: " +
92 + "\n" );
93
94 // output when file or directory was last accessed 95 outputTextBox.AppendText( "Last accessed: " +
96 + "\n" );
97 } // end method GetInformation 98 } // end class FileTestForm 99 } // end namespace FileTest
Fig. 17.5 | Using classesFileandDirectory.(Part 3 of 3.)
File.GetCreationTime( fileName )
File.GetLastWriteTime( fileName )
File.GetLastAccessTime( fileName )
b) Viewing all files in directoryC:\Program Files\
a) Viewing the contents of file"quotes.txt"
c) User gives invalid input d) Error message is displayed
Directoryto report the number of files of each file type that exist in the specified directory path. The program also serves as a “clean-up” utility—when it finds a file that has the.bak file-name extension (i.e., abackupfile), the program displays aMessageBoxasking the user whether that file should be removed, then responds appropriately to the user’s input. This example also usesLINQ to Objectsto help delete the backup files.
When the user clicksSearch Directory, the program invokessearchButton_Click
(lines 25–65), which searches recursively through the directory path specified by the user.
If the user inputs text in theTextBox, line 29 callsDirectorymethodExiststo determine whether that text is a valid directory. If it’s not, lines 32–33 notify the user of the error.
1 // Fig. 17.6: LINQToFileDirectoryForm.cs
2 // Using LINQ to search directories and determine file types.
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Windows.Forms;
7 using System.IO;
8
9 namespace LINQToFileDirectory 10 {
11 public partial class LINQToFileDirectoryForm : Form
12 {
13 string currentDirectory; // directory to search 14
15 // store extensions found, and number of each extension found 16
17
18 // parameterless constructor 19 public LINQToFileDirectoryForm()
20 {
21 InitializeComponent();
22 } // end constructor 23
24 // handles the Search Directory Button's Click event
25 private void searchButton_Click( object sender, EventArgs e )
26 {
27 // check whether user specified path exists 28 if ( pathTextBox.Text != string.Empty &&
29 )
30 {
31 // show error if user does not specify valid directory 32 MessageBox.Show( "Invalid Directory", "Error",
33 MessageBoxButtons.OK, MessageBoxIcon.Error );
34 } // end if
35 else
36 {
37 // use current directory if no directory is specified 38 if ( pathTextBox.Text == string.Empty )
39 currentDirectory = ;
Fig. 17.6 | Using LINQ to search directories and determine file types. (Part 1 of 4.)
Dictionary<string, int> found = new Dictionary<string, int>();
!Directory.Exists( pathTextBox.Text )
Directory.GetCurrentDirectory()
17.4 ClassesFileandDirectory 679
40 else
41 currentDirectory = pathTextBox.Text;
42
43 directoryTextBox.Text = currentDirectory; // show directory 44
45 // clear TextBoxes
46 pathTextBox.Clear();
47 resultsTextBox.Clear();
48
49 SearchDirectory( currentDirectory ); // search the directory 50
51 // allow user to delete .bak files 52 CleanDirectory( currentDirectory );
53
54 // summarize and display the results
55 foreach ( var current in )
56 {
57 // display the number of files with current extension 58 resultsTextBox.AppendText( string.Format(
59 "* Found {0} {1} files.\r\n",
60 , current ) );
61 } // end foreach
62 63
64 } // end else
65 } // end method searchButton_Click 66
67 // search directory using LINQ
68 private void SearchDirectory( string folder )
69 {
70 // files contained in the directory 71
72
73 // subdirectories in the directory 74
75
76 // find all file extensions in this directory 77 var extensions =
78 79 80
81 // count the number of files using each extension 82 foreach ( var extension in extensions )
83 {
84
85 // count the number of files with the extension
86 var extensionCount =
87 88 89 90 91
Fig. 17.6 | Using LINQ to search directories and determine file types. (Part 2 of 4.)
found.Keys
found[ current ]
found.Clear(); // clear results for new search
string[] files = Directory.GetFiles( folder );
string[] directories = Directory.GetDirectories( folder );
( from file in files
select Path.GetExtension( file ) ).Distinct();
( from file in files
where Path.GetExtension( file ) == extension select file ).Count();
92 // if the Dictionary already contains a key for the extension
93 if ( )
94 // update the count
95 else
96 // add new count
97 } // end foreach 98
99 // recursive call to search subdirectories 100 foreach ( var subdirectory in directories ) 101
102 } // end method SearchDirectory 103
104 // allow user to delete backup files (.bak) 105 private void CleanDirectory( string folder )
106 {
107 // files contained in the directory 108
109
110 // subdirectories in the directory 111
112
113 // select all the backup files in this directory 114 var backupFiles =
115 116 117 118
119 // iterate over all backup files (.bak) 120 foreach ( var backup in backupFiles )
121 {
122 DialogResult result = MessageBox.Show( "Found backup file " + 123 Path.GetFileName( backup ) + ". Delete?", "Delete Backup",
124 MessageBoxButtons.YesNo, MessageBoxIcon.Question );
125
126 // delete file if user clicked 'yes' 127 if ( result == DialogResult.Yes )
128 {
129 130 131
132 // if there are no .bak files, delete key from Dictionary 133 if ( found[ ".bak" ] == 0 )
134
135 } // end if
136 } // end foreach 137
138 // recursive call to clean subdirectories 139 foreach ( var subdirectory in directories ) 140
141 } // end method CleanDirectory 142 } // end class LINQToFileDirectoryForm 143 } // end namespace LINQToFileDirectory
Fig. 17.6 | Using LINQ to search directories and determine file types. (Part 3 of 4.)
found.ContainsKey( extension ) found[ extension ] += extensionCount;
found.Add( extension, extensionCount );
SearchDirectory( subdirectory );
string[] files = Directory.GetFiles( folder );
string[] directories = Directory.GetDirectories( folder );
from file in files
where Path.GetExtension( file ) == ".bak"
select file;
File.Delete( backup ); // delete backup file --found[ ".bak" ]; // decrement count in Dictionary
found.Remove( ".bak" );
CleanDirectory( subdirectory );
17.4 ClassesFileandDirectory 681
MethodSearchDirectory
Lines 38–41 get the current directory (if the user did not specify a path) or the specified directory. Line 49 passes the directory name torecursivemethodSearchDirectory(lines 68–102). Line 71 callsDirectorymethodGetFilesto get astringarray containing file names in the specified directory. Line 74 callsDirectorymethodGetDirectoriesto get astringarray containing the subdirectory names in the specified directory.
Lines 78–79 use LINQ to get theDistinctfile-name extensions in thefilesarray.
PathmethodGetExtensionobtains the extension for the specified file name. For each file- name extension returned by the LINQ query, lines 82–97 determine the number of occur- rences of that extension in thefilesarray. The LINQ query at lines 87–89 compares each file-name extension in thefilesarray with the current extension being processed (line 89). All matches are included in the result. We then use LINQ methodCountto determine the total number of files that matched the current extension.
ClassLINQToFileDirectoryFormuses aDictionary(declared in line 16) to store each file-name extension and the corresponding number of file names with that extension. A
Dictionary(namespaceSystem.Collections.Generic) is a collection ofkey–value pairs, in which eachkeyhas a correspondingvalue. ClassDictionaryis ageneric classlike class
List(presented in Section 9.4). Line 16 indicates that the Dictionary foundcontains pairs ofstrings andints, which represent the file-name extensions and the number of files with those extensions, respectively. Line 93 usesDictionarymethodContainsKeyto determine whether the specified file-name extension has been placed in theDictionary previously. If this method returnstrue, line 94 adds theextensionCountdetermined in lines 88–90 to the current total for that extension that’s stored in theDictionary. Other- wise, line 96 usesDictionarymethodAddto insert a newkey–value pairinto theDic- tionary for the new file-name extension and its extensionCount. Lines 100–101 recursively callSearchDirectoryfor each subdirectory in the current directory.
MethodCleanDirectory
When methodSearchDirectoryreturns, line 52 callsCleanDirectory(lines 105–141) to search for all files with extension.bak. Lines 108 and 111 obtain the list of file names and Fig. 17.6 | Using LINQ to search directories and determine file types. (Part 4 of 4.)
b) Dialog that appears to confirm deletion of a.bakfile
a) GUI after entering a directory to search and pressing Search Directory
list of directory names in the current directory, respectively. The LINQ query in lines 115–
117 locates all file names in the current directory that have the.bakextension. Lines 120–
136 iterate through the results and ask the user whether each file should be deleted. If the user clicksYesin the dialog, line 129 usesFilemethodDeleteto remove the file from disk, and line 130 subtracts 1 from the total number of.bakfiles. If the number of.bakfiles re- maining is0, line 134 usesDictionarymethodRemoveto delete the key–value pair for.bak files from theDictionary. Lines 139–140 recursively callCleanDirectoryfor each subdi- rectory in the current directory. After each subdirectory has been checked for .bakfiles, methodCleanDirectoryreturns, and lines 55–61 display the summary of file-name exten- sions and the number of files with each extension. Line 55 usesDictionarypropertyKeys to get all the keys. Line 60 uses theDictionary’s indexer to get the value for the current key.
Finally, line 63 usesDictionarymethodClearto delete the contents of theDictionary.