DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 11.5 Choosing a Method of Opening a File or Stream for Reading and/or Writing

Problem

When you are first learning the .NET Framework—and even for some time after—the proper way to read to, write from, or otherwise interact with files can be unclear because the framework provides so many different ways of attacking this problem. How should you determine which approach fits your scenario?

Solution

Use file streams to perform various file functions. There are five basic types of built-in file stream manipulation classes that you can use in order to read and/or write to the file stream:


FileStream

For the most fine-grained control, use FileStream for file manipulation since it provides the most low-level access to the file, and, therefore, the most complex actions become available. Some of these actions are reading and writing files in both synchronous and asynchronous fashions, methods to lock and unlock part or all of a file, seek a particular position in a file, or even read the file as a stream of either characters or bytes.


StreamReader

This type is derived from the abstract base class TextReader. The StreamReader class is designed for reading character or string input from a file. This class contains methods to read single characters, blocks of characters, lines of characters, or even the whole file into a single string variable.


StreamWriter

This class derives from the TextWriter class. It is designed for writing character or string output to a file. This class contains methods to write single characters or lines of characters.


BinaryReader

This type is derived from the Object class, as is the BinaryWriter class. It is designed for reading primitive data types—including byte or char data—from a file. This class contains methods to read any of the simple types (int, long, float, etc.), including char arrays and byte arrays.


BinaryWriter

This type derives from the Object class. It is designed for writing primitive data types—including byte or char data—to a file. This class contains methods to write any of the primitive types (int, long, float, etc.), including char arrays and byte arrays.

There are other stream readers and writers (XmlTextReader/Writer, StringReader/Writer) that can also perform file stream functions but at a higher level. This recipe is meant to give you a more fundamental approach to file operations.

Here are a few examples of using the various built-in streams:

// create a temp file to work with
string tempFile = Path.GetTempFileName( );

// FileStream
FileStream fileStream = null;
try
{
    // open the file
    fileStream = File.Open(tempFile,FileMode.Append);
    
    string text = "Hello World ";
    byte [] bytes = Encoding.ASCII.GetBytes(text.ToCharArray( ));
    
    // write to the file
    fileStream.Write(bytes,0,bytes.Length);
}
finally
{
    //make sure the file is closed if it was opened
    if(fileStream != null)
        fileStream.Close( );
}

// StreamReader
StreamReader streamReader = null;
try
{
    streamReader = new StreamReader(tempFile);
    char[] chars = new char[64];
    // read a block of characters
    streamReader.Read(chars,0,64);
    string charsFound = new string(chars);
    Console.WriteLine("Chars in stream {0}",charsFound);
}
finally
{
    if(streamReader != null)
        streamReader.Close( );
}

// StreamWriter
StreamWriter streamWriter = null;
try
{
    // open for append
    streamWriter = new StreamWriter(tempFile,true);
    // append some text
    streamWriter.WriteLine(", It's the StreamWriter!");
}
finally
{
    if(streamWriter != null)
        streamWriter.Close( );
}

// BinaryWriter
BinaryWriter binaryWriter = null;
long pos = 0;
int twentyFive = 25;
try
{
    // start up the binary writer with the base stream from the streamwriter 
    // since it is open
    binaryWriter = new BinaryWriter(streamWriter.BaseStream);
    // move to end
    pos = binaryWriter.Seek(0, SeekOrigin.End);
    // write out 25
    binaryWriter.Write(twentyFive);
}
finally
{
    // close up
    if(binaryWriter != null)
        binaryWriter.Close( );
}

// BinaryReader 
StreamReader streamReader2 = null;
BinaryReader binaryReader = null;
try
{
    // open a new reader
    streamReader2 = new StreamReader(tempFile);
    binaryReader = new BinaryReader(streamReader2.BaseStream);

    // advance the stream to the number we stored
    for(long i=0;i<pos;i++)
        binaryReader.ReadByte( );
    // read our number (should be 25)
    int num = binaryReader.ReadInt32( );
    // is this the same number...?
    if(num == twentyFive)
        Console.WriteLine("Successfully read 25 back from stream");
    else
        Console.WriteLine("Failed to successfully read 25 back from stream");
}
finally
{
    // close up
    if(binaryReader != null)
        binaryReader.Close( );
    // close stream
    if(streamReader2 != null)
        streamReader2.Close( );
}

Discussion

There are many different ways to create a stream. First, we will examine the FileStream class, referring to useful recipes that will help create objects of this type. We will then look at the StreamWriter and StreamReader classes, followed by the BinaryWriter and BinaryReader classes.

The most straightforward method of creating an object is to use the new keyword. The FileStream class has several overloaded class constructors that enable creating a new FileStream from scratch. The FileStream's constructor enables a new FileStream object to be created from either a filename or a file handle. See Recipe 11.19.

The FileStream constructor can also accept a FileAccess, FileMode, and/or FileShare enumeration value. These enumeration values are defined in Tables Table 11-2, Table 11-3, and Table 11-4, respectively.

Table 11-2. FileMode enumeration values

Value name

Definition

Specifics

Append

Opens an existing file and prepares it to be written to, starting at the end of the file. If the file does not exist, a new zero-length file is created.

This value can be used only in tandem with the FileAccess.Write enumeration value; otherwise, an ArgumentException is thrown.

Create

Creates a new file. If the specified file exists, it is truncated.

If you do not wish to lose data, consider employing the CreateNew enumeration value instead. This value can be used only in tandem with the FileAccess.Write or FileAccess.ReadWrite enumeration values; otherwise, an ArgumentException is thrown.

CreateNew

Creates a new file.

An IOException is thrown if the file already exists. This prevents accidental data loss. This value can be used only in tandem with the FileAccess.Write or FileAccess.ReadWrite enumeration values; otherwise, an ArgumentException is thrown.

Open

Opens an existing file.

A FileNotFoundException is thrown if the file does not exist. Use OpenOrCreate if it is possible that the file might not already exist.

OpenOrCreate

Opens a file if it exists or creates a new one if it does not exist.

Consider using Open if you expect the file to always exist before it is opened. An ArgumentException is not thrown if this enumeration value is used in tandem with the FileAccess.Read enumeration value.

Truncate

Opens an existing file and deletes all information in that file.

A FileNotFoundException is thrown if the file does not exist. This value can be used in tandem with the FileAccess.Write or FileAccess.ReadWrite enumeration values; otherwise, an ArgumentException is thrown.

Table 11-3. FileAccess enumeration values

Value name

Definition

Read

Allows data only to be read from a file.

ReadWrite

Allows data to be read from and written to a file. Same as FileAccess.Read | FileAccess.Write.

Write

Allows data only to be written to a file.

Table 11-4. FileShare enumeration values

Value name

Definition

Inheritable

Not supported in Win32.

None

The file cannot be accessed (read from or written to) or deleted by this or any other process.

Read

The file cannot be written to or deleted by this or any other process. It can be read from.

Write

The file cannot be read from or deleted by this or any other process. It can be written to.

ReadWrite

The file can be read from or written to by this or any other process. The file still cannot be deleted while it is being shared in this mode. Same as using FileShare.Read | FileShare.Write.

In addition to these enumerations that define how a file is opened, the FileStream constructor allows you to define whether this stream will be opened in a synchronous or asynchronous manner. This is the only class—of the ones discussed in this chapter—that allows a file to be opened in an asynchronous manner.

The FileStream class also has methods for seeking to a point within a file stream, as well as locking or unlocking a portion or an entire file; locking will prevent other processes or threads from modifying the file. The other stream types discussed in this chapter do not have the ability to lock or unlock portions or an entire file. This locking/unlocking functionality cannot even be accessed through the BaseStream property of any of these types. Seeking within a file can be done directly using the BinaryReader or BinaryWriter classes. The StreamReader and StreamWriter classes cannot directly access the seek functionality. However, by using the BaseStream property of either the StreamReader or StreamWriter classes, the base stream's seek functionality can be used.

FileStreams can also be created using the static methods of the File class. Table 11-5 shows these methods, along with their equivalent FileStream object constructor parameters.

Table 11-5. Static methods of the File class and their equivalent FileStream constructor calls

Static methods in File class

Equivalent FileStream constructor call

FileStream fileStream = 
File.Create("File.txt");
FileStream fileStream = new FileStream("File.txt", 
FileMode.Create, FileAccess.ReadWrite, FileShare.None);
FileStream fileStream = 
File.Open("File.txt");
FileStream fileStream = new FileStream("File.txt");
FileStream fileStream = 
File.OpenRead("File.txt");
FileStream fileStream = new FileStream("File.txt", 
FileMode.Open, FileAccess.Read, FileShare.Read);
FileStream fileStream = 
File.OpenWrite("File.txt");
FileStream fileStream = new FileStream("File.txt", 
FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);

The File.Open method is overloaded to accept FileMode, FileAccess, and FileShare enumeration values. The FileStream constructor is also overloaded to accept these same parameters. Therefore, to make an equivalent FileStream constructor for the File.Open method, we need to use the same parameters for each of these three enumeration values in both parameter lists.

The File class has a complementary class called FileInfo that contains similar methods, but these methods are instance, not static, methods. Table 11-6 shows the FileInfo methods, which are similar to the File static methods, along with their equivalent FileStream object constructor parameters.

Table 11-6. Instance methods of the FileInfo class and equivalent FileStream constructor calls

Instance methods in FileInfo class

Equivalent FileStream constructor call

FileInfo fileInfo = 
new FileInfo("File.txt");

FileStream fileStream = 
fileInfo.Create( );
FileStream fileStream = new FileStream("File.txt", 
FileMode.Create, FileAccess.ReadWrite, FileShare.None);
FileInfo fileInfo = 
new FileInfo("File.txt");

FileStream fileStream = 
fileInfo.Open(FileMode.open);
FileStream fileStream = new FileStream("File.txt");
FileInfo fileInfo = 
new FileInfo("File.txt");

FileStream fileStream = 
fileInfo.OpenRead( );
FileStream fileStream = new FileStream("File.txt", 
FileMode.Open, FileAccess.Read, FileShare.Read);
FileInfo fileInfo = 
new FileInfo("File.txt");

FileStream fileStream = 
fileInfo.OpenWrite( );
FileStream fileStream = new FileStream("File.txt", 
FileMode.OpenOrCreate, FileAccess.Write, FileShare.
None);

The FileInfo.Open instance method is overloaded to accept FileMode, FileAccess, and FileShare enumeration values. These values should be matched in the FileStream constructor parameter list.

The StreamReader and StreamWriter objects can be created using their overloaded constructors. These overloaded constructors accept as parameters either a file path and name or a FileStream object. Therefore, we can use any of the previously mentioned ways of creating a FileStream object in the construction of either a StreamReader or StreamWriter object.

In addition, we can use three of the static methods in the File class or three of the instance methods in the FileInfo class to create a StreamReader or StreamWriter object. Table 11-7 describes the static methods of the File class used to create StreamReader and StreamWriter objects and their equivalent StreamReader and StreamWriter object constructor parameters.

Table 11-7. Static methods of the File class and their equivalent StreamReader/StreamWriter constructor calls

Static methods in File class

Equivalent StreamReader/StreamWriter constructor calls

StreamReader streamReader = 
File.OpenText("File.txt");
StreamReader streamReader = new StreamReader("File.
txt");
StreamWriter streamWriter = 
File.AppendText("File.txt");
StreamWriter streamWriter = new StreamWriter("File.
txt", true);
StreamWriter streamWriter = 
File.CreateText("File.txt");
StreamWriter streamWriter = new StreamWriter("File.
txt", false);

Table 11-8 describes the instance methods of the FileInfo class used to create StreamReader and StreamWriter object and their equivalent StreamReader and StreamWriter object constructor parameters.

Table 11-8. Instance methods of the FileInfo class and their equivalent StreamReader/StreamWriter constructor calls

Instance methods in FileInfo class

Equivalent StreamReader/StreamWriter constructor calls

FileInfo fileInfo = 
new FileInfo("File.txt");

StreamReader streamReader = 
fileInfo.OpenText( );
StreamReader streamReader = new StreamReader("File.txt");
FileInfo fileInfo = 
new FileInfo("File.txt");


StreamWriter streamWriter = 
fileInfo.AppendText( );
StreamWriter streamWriter = new StreamWriter("File.txt", 
true);
FileInfo fileInfo = 
new FileInfo("File.txt");

StreamWriter streamWriter = 
fileInfo.AppendText( );
StreamWriter streamWriter = new StreamWriter("File.txt", 
false);

The methods of the File and FileInfo classes do not return BinaryReader and BinaryWriter classes; therefore, we rely on their constructors to create these types of objects. The overloaded BinaryReader and BinaryWriter class constructors accept only a Stream object; they do not accept a filename.

To create a BinaryReader or BinaryWriter object, we first need to create a Stream type object. Since Stream is an abstract class, we need to create one of its derived classes, such as the FileStream class. Any of the prior ways of creating a FileStream object may be employed as a parameter in the constructor of either a BinaryReader or BinaryWriter. The following code creates both a BinaryReader and a BinaryWriter object from a single FileStream object:

fileStream = File.Create("filename.file");
BinaryWriter binaryWriter1 = new BinaryWriter(fileStream);
BinaryReader binaryReader1 = new BinaryReader(fileStream);

There are many different ways of combining the techniques discussed in this recipe to create and open files. For example, if you require file locking and/or asynchronous file processing, you will need a FileStream object. If you are dealing with text streams in memory and on disk, perhaps the StreamReader and StreamWriter might be a better choice. Finally, if you are dealing with binary data or mixed binary and text data in different encodings, you should consider BinaryReader and BinaryWriter.

See Also

See Recipe 11.19; see the "FileStream Class," "StreamReader Class," "StreamWriter Class," "BinaryReader," and "BinaryWriter" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section