File Input and Output
Files are used to store information permanently on a computer. The simplest files to use are text files. Text files are sequential files that store information as simple strings of characters.
Sequential means that information is processed in order from beginning to end. A sequential file stores information in the order it is entered. The first piece of information is at the top of the file; the last piece of information is entered at the bottom of the file. If you want to read any information in a sequential file, you must read all the information first.
The StreamWriter class is used to write sequential, text files. Text files are read using the StreamReader class. Both of these classes, as well as a number of other file handling classes, are included in the System.IO namespace. The command using System.IO must be included in projects that will use these classes.
StreamWriter Class
Files are written using an object of the StreamWriter class. The constructor requires a file name, including a path, as a string. If only the file parameter is specified, a new file is created. This file will replace any existing file of the same name that may exist. If you wish to add to the end of an existing file, the second parameter of the constructor must be a Boolean value of true. Some programmers always use the second parameter, specifying false if they wish old files overwritten. Using both parameters makes it clear what the intentions of the programmer for the file.
Information is written to the file using Write and WriteLine methods just like the methods that write to the Console object. The following code opens a file and writes a series of lines of text. Notice that the constructor is passed a verbatim string so that the slash does not have to be doubled. The second parameter indicates that an existing file, if any, will not be replaced.
StreamWriter flOut = new StreamWriter(@"C:\testing.txt",true);
flOut.WriteLine("This is a sample text file");
for (int i=1;i<100;i++)
{
flOut.WriteLine("{0} \t {1:N5}\t {2,-8}", i, Math.Sqrt(i), Math.Pow(i,2));
}
Creating a new StreamWriter opens a block of memory called a buffer. Information is written first to the buffer. When the buffer is full, the information in the buffer is written to the surface of the disk. This is done for performance reasons. It is very inefficient to write small amounts of data to a disk drive. By writing only full buffers, the computer makes most efficient use of memory and the hardware for communicating between memory and the disk. The buffer is automatically written when the file is closed.
A file is automatically close and buffer written when the program exits normally. If a program exits abnormally, an untrapped exception is thrown for example, the buffer may not be properly closed .This can result in information being lost. For this reason, it is good practice to close a file when it is no longer being used. The Close method is used to close a file. A program can no longer write to a file once it has been closed. The following statement closes the file opened in the example above.
flOut.Close();
There are times when a program may not write to a file for a period of time but when it is not appropriate to close the file because opening and closing a file uses unneeded overhead. A programmer may want to have the program write the buffer to the disk in case any problems do develop. This is often the case when a programmer is writing information to a file to keep track of what a program is doing. The StreamWriter class provided the Flush method (shown below) to force the contents of a file buffer to be written to the disk.
flOut.Flush();
The StreamReader class is specifically designed for reading text files. It matches the StreamWriter class in many ways. Files written with the StreamWriter class are easily read with the StreamReader class.
Just as files must be opened by creating a StreamWriter object, files are read after being opened by creating an object of the StreamReader class. The StreamReader constructor requires a string carrying the name and path of the file to open.
There are several methods that can be used to read the file but the simplest to use is the ReadLine method. The ReadLine method reads all the characters in a line. The carriage return and line feed that terminate each line in a text file are ignored. The text in the line is returned as a string. If a line is empty then an empty string is returned. An exception is not thrown if an attempt is made to read past the end of the file. At the end of the file a null reference is returned. The following code reads all the lines in a file and displays them on the Console. The loop ends when a null reference is returned.
StreamReader flIn = new StreamReader(@"C:\testing.txt");
string sLine;
do
{
sLine = flIn.ReadLine();
Console.WriteLine(sLine);
} while (sLine != null);
The Read method has two primary overloads. The default method returns an integer that represents the next character in the file. The second Read overload takes three parameters. The first parameter is an array of type char that will serve as a buffer to copy data from the file. The second parameter is the offset in the buffer to start copying the information from the file. The third parameter is a count of how may characters should be read and copied into the buffer. There must be room in the buffer for the information being read. This Read method returns an integer value that represents the number of characters that were actually read. This value will be the number of characters requested unless there are not enough characters in the file to read. In that case, the actual number of characters read will be returned.
The following code example reads a text file in blocks of ten characters. Because the Read method does not ignore the carriage and line feed at the end of each line, the output of this example will move to a new line when for each line in the file even though the Write method is used for the Console.
string sLine;
char [] cLine = new char[10];
int iLine;
do
{
iLine = flIn.Read(cLine,0,10);
Console.Write(cLine);
} while (iLine == 10);
The StreamReader class has a Close method like the one StreamWriter. Calling the Close method frees any system resources that were used to read the file. As with the StreamWriter class, attempting to read from a file that has been closes will cause an error. Sequential files can only be read in one direction. If your program needs to re-read information the file must be closed and then opened again.
File Information
The FileInfo class is used to get information about a file. This class also includes a method for deleting a file. An object of the file class is constructed using a string with a file name and path. For example:
FileInfo myFile = new FileInfo(@"C:\testing.txt");
Once a FileInfo object has been created, FileInfo properties can be used to return information about the file. The following method takes a file name and reports some information about the file. You will notice that it first checks the Exists property to make sure the file exists.
static void showFile(string xFile)
{
FileInfo myFile = new FileInfo(xFile);
if (myFile.Exists)
{
Console.WriteLine("File Name {0}: ", myFile.Name);
Console.WriteLine("File Directory {0}: ", myFile.Directory);
Console.WriteLine("File Length {0}: ", myFile.Length);
}
else
Console.WriteLine("{0} does not exist",xFile);
}
The values in the FileInfo object properties are set when the object is constructed. These values are not updated automatically. The Refresh method can be used to update the property values.
Some useful properties and methods are listed in the table below.
Member |
Description |
|
CopyTo |
Creates a new copy of the file - void method that takes the new location as a string parameter, a Boolean parameter indicates if an existing file is to be overwritten. If the second parameter is not used an existing file will not be overwritten |
|
Delete |
Deletes a file - void method Files cannot be deleted if they are open. |
|
DirectoryName |
The full path of the file - string property |
|
Exists |
Returns true if the file exists; false if the file does not exist - Boolean property |
|
FullName |
Returns the full name and path of the file - string property |
|
IsDirectory |
Indicates if the object is a directory - Boolean property |
|
IsFile |
Indicates if the object is a file - Boolean property |
|
Length |
Returns the size of the file or directory - long property |
|
MoveTo |
Moves the file to a new location and potentially a new name - void method that takes the new location as a string parameter |
|
Refresh |
Updates the information in the object - void method |
The length property tells the size of a file in bytes. Each byte is a character. The system does not keep track of the number of lines in a text file. If all the lines are the same length then the Length property can be used to calculate the number of lines by dividing it by the length of a line. Many files have variable length lines and this method will not work with those files.
Design Considerations
Programs that read data from text files must be carefully designed to anticipate the format of data in a file. Programs should also be designed to handle unexpected data. Text files like the ones used with StreamWriter and StreamReader can also be read and written with text editors such a Notepad. Notepad is a useful tool for verifying the contents of files written by a program. Programs like Notepad will also allow you to check the contents of files before you write a program that has to read the file for its data.
Reading files is made easier if the data in them was written by a program whose actions are well documented and consistent. It is good practice to write a single method that formats data for outputting. A special method may be used or the ToString method may be over ridded in a class and its output used to write data. Similarly, methods may be written to parse a line of text from a file, split it individual parts and place values in the correct format.
As an example, consider a simple Student class. This class has four data values: first and last name, age, and year of graduation. An overload of the ToString method could build a string that included each value with a space or other separating character between each field. It might look something like the following.
public override string ToString()
{
return (firstName + " " + lastName + " " + age.ToString() + " " + yog.ToString());
}
A program would write the whole set of data for an object using a single WriteLine line the following.
flOut.WriteLine(testStudent.ToString());
With the data in a file written in a consistent format using a common routine, it is easy to parse data from a line of text and place it in proper fields. The split method is used to separate fields. The example below uses a space as a separator. If a field needed to contain a space then some other character, such as a comma, dash, slash or other character that was not used in a data field would be used.
Once the fields are placed in a string array, individual fields can be converted to the correct format and stored.
public void readData(string inData)
{
if (inData != null)
{
char [] spl = new char [] {' '};
string [] working = inData.Split(spl);
if (working.Length == 4)
{
this.fName = working[0];
this.lName = working[1];
this.Age = Convert.ToInt32(working[2]);
this.Yog = Convert.ToInt32(working[3]);
}
else
throw (new Exception("Missing field(s)"));
}
else
throw (new Exception("Null record passed"));
}
Notice that this code checks for several errors. If a null string is passed of if the string passed does not have enough fields the complete data set cannot and should not be saved. In those cases, exceptions are thrown. These exceptions should be caught in the method that calls this one.
Summary
Declaring a new StreamWriter object with a file name and path opens a file for writing. The programmer has the option to add to the end of and existing file or replace the file with a new file. The StreamWriter class provides a Write and WriteLine methods that are similar to the methods of the same name that are used by the Console object. Information is written to a file buffer that is copied to the disk when the buffer it full, the buffer is flushed with the Flush method or the file is closed.
Files are opened for reading by declaring a new StreamReader object. The ReadLine method returns a line of text as a string. The Read method returns a single char or an array of char as specified by the programmer. The StreamReader object, like the StreamWriter object, should be closed when no longer needed.
The File class provides methods for moving, copying and deleting files. The file class also uses properties to provide information about a file or directory.
Review Questions
Buffer A buffer is a location in the computer’s memory that is set aside to store information used to read and write to a file.
Sequential Sequential information must be read or written in order. For a program to read the third item in a sequential file it must first read the first and second item. A program cannot skip to a specific place in a sequential file.
Projects
For each student, list the student's name, the number of correct answers, and a letter grade under appropriate headers. Calculate the letter grade for each student. Nine or ten correct answers is an A. Eight correct answers is a B. Seven correct answers is a C. Six correct answers is a D. Less than six correct answers is an F.
Calculate the average number of correct answers for the class and report it at the bottom of the report.