ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


C# Input/Output Classes Simplified

by Budi Kurniawan
08/05/2002

Beginners to the .NET Framework sometimes have a hard time trying to understand the classes in the System.IO namespace for performing input/output (IO) operations. The difficulty stems from the fact that this namespace is relatively large, containing more than 40 members, some of which are similar classes that can be used to achieve the same tasks. Thus it is sometimes hard to figure out which class is best for which task. This article tries to make your life a bit easier by grouping IO tasks into three categories and introducing the classes that are suitable for each task category.

Input/output (IO) refers to the operations for reading and writing data to streams and files. In the .NET Framework, you use the classes in the System.IO namespace to perform these operations. This article starts with the basic classes such as the File and FileInfo classes -- classes used to perform file operations such as creating, copying, and deleting files. Other basic classes include Directory and DirectoryInfo, which provide functionality to manipulate directories, and Path, which supports application portability. In addition to those classes, there are also classes you can use to read and write the contents of a file. The System.IO namespace provides a number of classes that allow you to do basically similar things. These classes can be roughly grouped into three categories:

Related Reading

C# in a Nutshell
By Peter Drayton, Ben Albahari, Ted Neward

In the first group are stream classes that are derived from the Stream class. In the second category are reader and writer classes, such as StreamReader and StreamBuffer. The third group includes classes such as BinaryReader and BinaryWriter. Which class you should use for your input and output operations will depend on the kind of data you are going to work with.

The File Class

You use the File class to manipulate a file; to create, copy, or delete it, and check if it exists. You also use it to open a file for reading and writing. To read from and write to a file itself, however, you need to use other classes, such as the FileStream or the BufferedStream class. The File class constructor is private, so you cannot instantiate a file using the new keyword. This is not necessary because all methods of the File class are static. These static methods perform security checks every time they are called. This, of course, has performance consequences, and sometimes is not needed. If you need to access the same file frequently, use the FileInfo class instead. The following is the list of the most important methods of the File class:

Create

Creates a file in a specified directory. You call this method passing a file path or a file path and an integer of a buffer size for the resulting FileStream object. For example, the following code creates a file called newFile.txt in the C:\data directory, returns a FileStream, and then closes the FileStream object straight away:


FileStream fs = File.Create("C:/data/newFile.txt");
fs.Close();

For more information on the FileStream class, see the section "The FileStream Class" later in this article. As another example, the following code creates a FileStream with a buffer size of 16KB.


FileStream fs = File.Create("
     C:/123data/newFile.txt", 16384);

If the file c:\data\newfile.txt already exists, it will be overwritten.

CreateText

Creates a file and returns a StreamWriter object that you can use to write text into that file. Like Create, you pass a file path to the method, and any existing file will be overwritten. For example:


StreamWriter sw = File.CreateText("
     C:/123data/new.txt");

See the "The StreamWriter Class" section later in this article for more details on StreamWriter.

AppendText

Returns a StreamWriter object to a specified existing file. The text you write will then be appended to the end of the file. If the file does not exist, AppendText will create a new file. You pass a file path to this method.

Exists

Returns true if a file in a specified path exists; otherwise, it returns false. Its only parameter is a path and filename.

Copy

Copies a file to another folder. There are two overloads of this method, as follows:


public static void Copy(
  string sourceFileName,
  string destFileName
);
public static void Copy(
  string sourceFileName,
  string destFileName,
  bool overwrite
);

The first overload of the method will throw an IOException if the destination file already exists. So will the second overload, if the overwrite argument is false. For both overloads, a FileNotFoundException will be thrown if the source file does not exist. In addition, the method can throw an ArgumentException if either sourceFilename or destinationFilename is invalid.

Move

Moves a file to another directory. The Move method has the same syntax as the first overload of the Copy method. The difference from the Copy method is that Move deletes the source file.

Open

Opens a file and returns a FileStream object. If the file to be opened does not exist, a FileNotFoundException exception is thrown. This method has three overloads, all of which require that you pass a path and filename as the first argument and a member of the FileMode enumeration as the second. The second and third overloads require a member of the FileAccess enumeration as their third argument, while the third overload requires a member of the FileShare enumeration as its fourth argument.

The FileMode enumeration tells the operating system how the file should be opened. Its members are Append, Create, CreateNew, Open, OpenOrCreate, and Truncate.

Related Reading

Programming C#
By Jesse Liberty

The FileAccess enumeration indicates to the operating system whether the file is to be opened for reading or writing. Its members are Read, ReadWrite, and Write. The FileShare enumeration tells the operating system how to control access to the same opened file from other threads, processes, or users. Its members are Inheritable, None, Read, ReadWrite, and Write. If the value of the argument is FileShare.Write, for example, other programs can write to the opened file.

OpenRead

Opens a file as read-only and returns a FileStream object. Its only parameter is the path and name of the file. This is a single-purpose version of the Open method.

OpenText

Opens a file and returns a StreamReader object. Its only parameter is the path and name of the file.

OpenWrite

Opens a file and returns a Stream object. You can use this Stream object to read and write to the file. The method's only parameter is the path and name of the file.

GetAttributes

Obtains the file's attributes, as expressed in a bitwise combination of the members of the FileAttributes enumeration: Archive, Compressed, Device, Directory, Encrypted, Hidden, Normal, NotContentIndexed, Offline, ReadOnly, ReparsePoint, SparseFile, System, and Temporary.

SetAttributes

Changes the attribute of a file. You pass a file path and a bitwise combination of the members of the FileAttribute enumeration to this method.

The FileInfo Class

The File class has static methods that can be conveniently used without having to have an instance of a File object; however, those methods always perform security checks that adversely affect performance. The FileInfo class provides similar methods for similar tasks, but without security checks. You should use FileInfo if you are going to access a file several times. Also, unlike File, methods in FileInfo are instance methods. To create a FileInfo object, pass a valid file path to its constructor. The Create method, for example, behaves like the Create method of the File class. For instance, the following code uses the FileInfo class to create a file:


FileInfo fileInfo = new FileInfo("C:/NextGen.txt");
FileStream fs = fileInfo.Create();

The Directory Class

The Directory class represents a directory. Its constructor is private, so you cannot use the new keyword to construct a Directory object. That is not necessary, in any case, because all methods of the Directory class are static. The Directory class performs security checks on all of its methods, resulting in slightly reduced performance. If you will be accessing the same directory often, use the DirectoryInfo class instead; it is described in the section "The DirectoryInfo Class."

The following are some of the important methods of the Directory class:

CreateDirectory

Creates a directory in the specified path. If the directory's parent does not exist, the parent directory will also be created. For example, if you pass the path C:\data\projects\RD to this method and the projects directory does not exist, both the RD and projects directories will be created. The return value of this method is a DirectoryInfo object representing the created directory. For example:


DirectoryInfo dirInfo = Directory.CreateDirectory("
     C:/data/Projects/RD");
System.Console.WriteLine(dirInfo.Name); // prints "RD"

For more information about DirectoryInfo, see the "The DirectoryInfo Class" section.

Delete

Deletes an existing directory and, optionally, all of its subdirectories and files. If the directory does not exist, a DirectoryNotFoundException will be thrown. This method has two overloads, as follows:


public static void Delete(string path);
public static void Delete(string path, bool recursive); 

The first overload allows you to pass an empty directory to delete. If the directory is not empty, an IOException will be thrown. The second overload enables you to delete a directory and all of its subdirectories and files, if the recursive argument is set to true.

Exists

Returns true if the specified directory exists; otherwise, it returns false. Its single argument is the path to the directory.

GetDirectories

Returns an array of strings containing all of the subdirectories of a directory. The first overload is the most straightforward; it accepts a valid path to the directory, the subdirectories of which you want to obtain. The second overload allows you to specify a pattern as the second argument. For example, with the second overload, you can obtain only subdirectories that have names beginning with a certain substring, as illustrated by the following code, which prints all of the subdirectories of the C:\ drive that start with the letter W or w:


string[] dirs = Directory.GetDirectories("C:\\", "W*");
int count = dirs.Length;
for (int i=0; i<count; i++)
  System.Console.WriteLine(dirs[i]);

GetFiles

Returns all of the files in a directory. Like the GetDirectories method, this method has two overloads. The first overload returns an array of Strings containing the filenames in a directory. The second overload allows you to specify a pattern as the second argument. For example, the following code prints all of the files in the C:\ drive that start with the letter A or a:


string[] files = Directory.GetFiles("C:\\", "A*");
int count = files.Length;
for (int i=0; i<count; i++)
  System.Console.WriteLine(files[i]);

GetLogicalDrives

Returns an array of Strings containing all of the logical drive names of the current computer in the format <drive_letter>:\. For example, the following code prints all of the drive names in the computer:


string[] drives = Directory.GetLogicalDrives();
int count = drives.Length;
for (int i=0; i<count; i++)
  System.Console.WriteLine(drives[i]);

GetParent

Returns a DirectoryInfo object representing the parent directory of the specified directory. Its single argument is the path of the directory, the parent of which is to be returned.

Move

Moves a specified directory and all of its subdirectories and files to a new location. This method accepts two arguments: the path of the source directory, and the path to the new location.

GetCurrentDirectory

Returns a String object containing the full path to the current directory. Ths method has no parameters.

SetCurrentDirectory

Sets a new current directory. This method's only argument is the path of the directory to make current.

The DirectoryInfo Class

The Directory class provides a convenient way of manipulating directories. The convenience comes at a price, however: the Directory class performs security checks on all of its methods. If you are going to access the same directory multiple times, you can avoid these security checks by using the DirectoryInfo class, which provides methods similar to those in the Directory class, but with all of its methods instance methods. Therefore, you must first construct a DirectoryInfo object by passing a path to its constructor, as in the following code:


DirectoryInfo dirInfo = new DirectoryInfo("C:/data");

Constructing a DirectoryInfo object does not mean creating a directory if it does not yet exist. To create a directory, you need to call the Create method, as shown in the following code:


DirectoryInfo dirInfo = new DirectoryInfo("
     C:/XFiles.txt");
dirInfo.Create();

If the directory already exists, the call to the Create method does not throw an exception. If, after constructing a DirectoryInfo object representing a non-existent directory, however, you call other methods that require that the directory exist, a DirectoryNotFoundException will be thrown. For example, the Delete, GetDirectories, GetFile, and MoveTo methods require the DirectoryInfo object to represent a directory that exists.

The Path Class

The Path class provides properties and methods that you can use to write portable applications, when your applications need to run on more than one operating system. As you may be aware, different operating systems have different directory and file naming conventions. For example, the Windows operating system recognizes the back-slash (\) character as a directory separator. In Unix or Linux, however, the forward slash (/) character is used to separate a directory from a subdirectory. By using the Path class, you can write applications that will run on multiple operating systems without changing your code. For example, the static field DirectorySeparatorChar translates into the directory separator character of the operating system on which the application is run. Therefore, if you need to access a file called flower.gif in a subdirectory named images, you can specify the following string as a relative path to the file:


string path = "images" + Path.DirectorySeparatorChar 
     + "Flower.gif";

The following list give the other static fields.

PathSeparator

Returns a character that is used to separate paths in the current operating system. For Windows, this is the semicolon (;) character.

AltDirectorySeparatorChar

Returns a character which is the alternate directory separator character for the current operating system. In Unix or Linux, this character is the back slash (\). In Windows and Macintosh, this character is the forward slash (/).

InvalidPathChars

Returns an array of characters that cannot be used in a path in the current operating system. For instance, in Windows, you cannot have a filename that contains an asterisk or a question mark. You can use this field to verify the validity of a filename entered by the user.

The Stream Class

The Stream class is an abstract class that is implemented by BufferedStream, FileStream, MemoryStream, NetworkStream, and CryptoStream. The two child classes that are frequently used and will be discussed in this section are FileStream and BufferedStream.

Even though you cannot instantiate a Stream object directly, sometimes you obtain a Stream object from a method, such as the OpenFile method of the System.Windows.Forms.OpenFileDialog class. To improve performance, you might want to wrap the Stream object inside of a BufferedStream object. For more information about the BufferedStream class, see the section "The BufferedStream Class."

The Stream class provides functionality that enables programmers to manipulate a Stream. For example, the Length property returns the number of bytes in the Stream object, and the Position property specifies the position of the pointer in the stream. Three other properties are CanRead, CanWrite, and CanSeek, all of which are self-explanatory. Among others, the Stream class provides methods for reading and writing. When a read or write operation is performed, the position of the Stream is advanced by the number of bytes read or written. The following are some are some of the more important methods of the Stream class.

Close

Closes the Stream object and frees all of the resources it consumes. Even though unused resources will eventually be garbage collected, it is good programming practice to always call this method when you no longer need a Stream object.

Flush

Forces the contents of the buffer to be written to the underlying device. For example, in a FileStream, this method writes the buffer to the file.

Read

Reads available data to an array of bytes. This method returns an Integer that indicates the number of bytes read. If the pointer is at the end of the stream when Read is called, the method will return zero. The method has the following signature:


public abstract int Read(
  in byte[] buffer,
  int offset,
  int count
)

The bytes read will be placed in the buffer. You can specify the maximum number of bytes that can be read in count. If count is greater than the number of available bytes, only the available bytes will be returned. The offset argument specifies the number of bytes that need to be skipped before starting to read. If you specify 0 for offset, the read operation will start from the byte pointed to by the stream pointer.

ReadByte

This method reads one byte and casts it into an integer, which is returned by the method. If the end of the stream has been reached when this method is called, a -1 is returned. The method has no parameters.

Write

This method writes an array of bytes into the stream. It does not return any value, and has the following signature:


public abstract void Write(
  byte[] buffer,
  int offset,
  int count
)

The Write method writes the buffer byte array into the stream. It will write the number of bytes specified by count and skip the offset number of bytes in buffer before starting the write operation. Note that count must not be greater than the number of available bytes in the buffer.

WriteByte

Writes a single byte to the stream. This method accepts a byte as its argument and does not return a value.

The FileStream Class

The FileStream class represents a stream that wraps a file. You use FileStream for reading and writing bytes of a file. The FileStream class uses buffering to improve performance. The easiest way to construct a FileStream object is to use one of its constructors, which accepts a file path and a value from the FileMode enumeration as arguments. For instance, the following code creates a file called newFile.txt and writes two bytes into it. If the file already exists, the file will be overwritten:


FileStream fs = new FileStream("C:/newFile.txt", 
     FileMode.Create);
Byte[] bytes = new Byte[2];
bytes[0] = 65;
bytes[1] = 66;
fs.Write(bytes, 0, 2);
fs.Close();

Some methods return a FileStream object without requiring that you create it with one of its constructors. For example, the following code uses the File class' Create method to create a file called newFile.txt, returns a FileStream object, and then writes two bytes to the file:


FileStream fs = File.Create("C:/newFile2.txt");
Byte[] bytes = new Byte[2];
bytes[0] = 65;
bytes[1] = 66;
fs.Write(bytes, 0, 2);
fs.Close();

If you want to append to a file instead of creating a new one, you must use the FileStream constructor that accepts a FileMode and a FileAccess value, and you must specify FileAccess. Write for the file access type, as shown in the following code:


FileStream fs = new FileStream("C:/newFile.txt", 
     FileMode.Append, FileAccess.Write);

To read from a file, you can use FileMode. Open as the file mode argument in one of the FileStream class' constructors. The following code, for instance, reads two bytes from the newFile.txt file:


FileStream fs = new FileStream("C:/newFile.txt", 
     FileMode.Open);
Byte[] bytes = new Byte[2];
int i = fs.Read(bytes, 0, 2);
fs.Close ();

The BufferedStream Class

The BufferedStream class is used to wrap a Stream object and supports buffering to improve performance. There are two constructors that you can use to create a BufferedStream object. The first one accepts a Stream object. The BufferedStream object constructed using this constructor has a buffer size of 4,096 bytes. The second constructor accepts a Stream object as well as an Integer specifying the size of the buffer, allowing you to set your own buffer size.

As an example of how to use the BufferedStream, consider the following code, which wraps the Stream object returned from an OpenFileDialog in a BufferedStream object and reads the first 128 bytes of the selected file:


    OpenFileDialog openFileDialog = new OpenFileDialog();
      if (openFileDialog.ShowDialog() == DialogResult.OK) 
      {
        string filepath = openFileDialog.FileName;
        // Open the file using the OpenFile method
        BufferedStream bufferedStream = new BufferedStream(
		     openFileDialog.OpenFile());
        Byte[] bytes = new Byte[128];
        bufferedStream.Read(bytes, 0, 128);
      }

Note that the BufferedStream class is derived from the Stream class; therefore, it inherits all of the Stream class' methods and properties.

The StreamReader Class

The StreamReader class is a child class of TextReader. You use StreamReader and other Reader classes to manipulate text. With this class, you can use the Read method to read one character each time or a fixed number of characters, or you can use the ReadLine method to read a line of text. You can construct a StreamReader object from a Stream object returned by a method call, or by specifying a path to a file to one of the StreamReader class constructors.

Optionally, you can also pass an encoding scheme to a constructor to tell the StreamReader object the encoding used in the text file. If no encoding is supplied, the default encoding (System.Text.UTF8Encoding) is assumed. The stream's current encoding can be retrieved from the read-only CurrentEncoding property.

As an example, the following code opens a text file named CompanySecret.txt and uses the ReadLine method to print the text to the Debug window one line at a time. The ReadLine method returns a null reference if it encounters the end of the stream:


    StreamReader sr = new StreamReader("
	     C:/CompanySecret.txt");
      string line = sr.ReadLine();
      while (line != null) 
      {
        System.Console.WriteLine(line);
        line = sr.ReadLine();
      }
      
      sr.Close();

Note that when you read a line of text using the ReadLine method, the end-of-line character is not included in the returned String.

The StreamWriter Class

The StreamWriter class is derived from the TextWriter class and can be used to write text to a stream. You can create an instance of the StreamWriter class by assigning it a Stream object returned by a method call, or by passing a file path to one of its constructors. Like the StreamReader class, you can also pass an encoding scheme of your choice to one of its constructors. The most important methods of the StreamWriter class are Write and WriteLine. Write enables you to write a character, an array of characters, or a string. The WriteLine method accepts a String and adds an end-of-line character to the end of the string. For example, the following code opens a new file called NewCompanySecret.txt and writes a string of text to it:


StreamWriter sw = new StreamWriter("
     C:/NewCompanySecret.txt");
string line = "Don't tell anyone!!!";
sw.WriteLine(line);
sw.Close();

The BinaryReader Class

You use the BinaryReader class to read binary data from a file. You can construct a BinaryReader object by passing its constructor a Stream object; you can't pass a file path to create a BinaryReader object. Once you have a BinaryReader object, you can use one of its ReadXXX methods to read various types of data. For example, the ReadByte method is used to read a byte, and the ReadString method is used to read a String. The data must be read in the order it was written. For example, if you store a byte and then a string to a binary file, you must first read a byte and then read a String. The BinaryReader class also has a Read method, which has three overloads that allow you to read a character, an array of bytes, or an array of characters. As an example, here is the code that reads a byte and a string from a binary file called MyBinary.bin:


BinaryReader br = new BinaryReader(File.OpenRead("
     C:/MyBinary.bin"));
Byte b2 = br.ReadByte();
string s2 = br.ReadString();
System.Console.WriteLine(s2);
System.Console.WriteLine(b2);
br.Close();

The BinaryWriter Class

The BinaryWriter class is used to write binary data to a stream. You can construct a BinaryWriter object by passing a Stream object; you canít pass a file path to create a BinaryWriter object. In addition, there is also a parameterless constructor. The BinaryWriter classí most important method is Write. This method has 18 overloads that guarantee that you can write any type of data to a BinaryWriter object. For example, the following code creates a BinaryWriter object that is linked to a new file called MyBinary.bin, and writes a byte and a string:


BinaryWriter bw = new BinaryWriter(File.Create("
     C:/MyBinary.bin"));
Byte b = 17;
string s = "August";
bw.Write(b);
bw.Write(s);
bw.Close();

Budi Kurniawan is a senior J2EE architect and author.


Return to .NET DevCenter

Copyright © 2009 O'Reilly Media, Inc.