Introduction to Basic File I/O
In the .NET Framework, file I/O is accomplished using streams. Although some classes may make certain aspects of reading and writing to text files easier, those classes still make use of streams for the final read and write operations on operating system files. This section shows you how to create, append to, read from, and query information about files. You will also see how the framework provides utility classes for common file operations, such as the StreamWriter class that is used in conjunction with the File class to make working with plain text files easy.
Creating and Appending Files
When working with files using the Stream pattern, the majority of the work is done in the constructor for the FileStream class. This constructor allows you to specify the filename (or a classic Win32 file handle), as well as access modes, sharing modes, and much more. Using the constructor, you indicate whether you want to create a new file or open an existing file, or open an existing file for appending.
The following few lines of code create a new file and write some text to it:
string origString = "I never saw an author who was aware that there is any "+ "dimensional difference between a fact and a surmise.\n"+ " - Mark Twain"; // create the file, write to it, save it. FileStream fs = new FileStream("quote.txt", FileMode.Create); fs.Write(ASCIIEncoding.ASCII.GetBytes(origString),0, origString.Length); fs.Close();
Note that you need to close the FileStream in order for the contents of the Stream to be written to disk. As mentioned in the description for the Stream class, the Close method releases all resources and flushes the remaining contents of the buffer to whatever underlying media backs the stream, such as a disk file.
You can use a different option in the constructor to obtain a FileStream for the same file, but this time you can use the Stream to append additional data to the file:
string addString = "\n\nGood friends, good books and a sleepy conscience: "+ "this is the ideal life.\n" + " - Mark Twain"; fs= new FileStream("quote.txt", FileMode.Append); fs.Write(ASCIIEncoding.ASCII.GetBytes(addString), 0, addString.Length); fs.Close();
The preceding code uses the FileMode.Append enumeration item to indicate how the file should be opened. Table 7.3 contains a description of each of the possible file modes.
Now that you have seen the low-level way of creating basic files, whether they are binary files or text files, let's take a look at a quicker and easier way of working with text files.
You can replace the preceding code where you have to work with arrays of bytes with the following code, making the code easier to read and simpler to write:
StreamWriter sw = File.CreateText("quote2.txt"); sw.Write(origString); sw.Write(addString); sw.Close();
As you can see, the code is a lot simpler than the previous examples. The reason this chapter started off showing you how to work with arrays of bytes is that this knowledge will help you if you need to work with files that don't contain simple text, such as image files or binary files containing fixed data structures.
Reading from Existing Files
Reading from files using streams works just like all other I/O that has been discussed in this chapter up to this point. You obtain a reference to the file either using the FileStream class constructor or using the File class.
When you have a reference to the file from which you want to read, you can read that data using the stream's Read or ReadByte methods.
The following code uses a FileStream class to open an existing file, read an array of bytes from it, and display the resulting array of bytes as an ASCII string:
byte fileBytes = new byte[origString.Length + addString.Length]; fs = new FileStream("quote.txt", FileMode.Open); fs.Read(fileBytes, 0, (int)fs.Length); Console.WriteLine("Quote from the file:"); Console.WriteLine(ASCIIEncoding.ASCII.GetString(fileBytes));
And now take a look at the same effect using the OpenText method of the File class to create an instance of the StreamReader class:
StreamReader sr = File.OpenText("quote2.txt"); Console.WriteLine("Entire file: \n" + sr.ReadToEnd()); sr.Close();
Using Directories and the File System
In all the previous examples, the filename of the file with which we were working was known and fixed. This isn't exactly a good model of reality. In most commercial applications that deal with files, you need to do things like check to make sure that a file exists, or check to see if a specific directory exists. If the directory doesn't exist when you need to create the file, you need to create the directory as well.
To help developers deal with this, the System.IO namespace provides the following classes:
For more information on each of these individual classes, you can refer to the MSDN documentation online at http://msdn.microsoft.com. The example in Listing 7.2 illustrates how to test and see if a file exists, and if it does, create a subdirectory, copy the file there, and then display some operating system-level information about the file itself.
Listing 7.2. File Manipulation and Query
The core of the code starts with the instantiation of a new FileInfo object, from which you can perform many different operations (such as a copy) as well as query detailed information about the file itself.
Developers who are familiar with file-level programming in previous versions of C# may start to get a little giddy when they notice that you can now obtain an ACL (Access Control List) starting with the FileInfo object. Previously, obtaining the list of access rights associated with a file was a painful and tedious task that involved invoking the Win32 API using a technique (discussed in Chapter 13, "COM and Windows Interoperability") called "Platform Invoke."
The output of the preceding code looks as follows:
File: D:\SAMS\C# Unleashed 2005\Chapters\07\Code\DirManip\DirManip\bin\Debug\quote.txt Location: D:\SAMS\C# Unleashed 2005\Chapters\07\Code\DirManip\DirManip\bin\Debug Created:11/7/2005 Owner: Everyone User: Everyone Type: Allow