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?
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( ); }
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
Table 11-4. FileShare enumeration values
Value name |
Definition |
---|---|
|
Not supported in Win32. |
|
The file cannot be accessed (read from or written to) or deleted by this or any other process. |
|
The file cannot be written to or deleted by this or any other process. It can be read from. |
|
The file cannot be read from or deleted by this or any other process. It can be written to. |
|
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 |
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
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
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
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
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 Recipe 11.19; see the “FileStream Class,” “StreamReader Class,” “StreamWriter Class,” “BinaryReader,” and “BinaryWriter” topics in the MSDN documentation.