You can find the wrox.com
code downloads for this chapter at www.wrox.com/go/beginningvisualc#2015programming
on the Download Code tab. The code is in the Chapter 18 download and program names match the names used in the examples throughout the chapter.
Files can be a great way to store data between instances of your application, or they can be used to transfer data between applications. User and application configuration settings can be stored to be retrieved the next time your application is run.
This chapter shows you how to use files effectively in your applications, touching on the major classes used to create, read from, and write to files, and the supporting classes used to manipulate the file system from C# code. Although you won't examine all of the classes in detail, this chapter goes into enough depth to give you a good idea of the concepts and fundamentals.
Reading and writing files is an essential way to get data into your C# program (input) and send data out of your program (output). Because files are used for input and output, the file classes are contained in the System.IO
namespace. (IO is a common abbreviation for Input/Output.)
System.IO
contains the classes for reading and writing data to and from files, and you can reference this namespace in your C# application to gain access to these classes without fully qualifying type names.
The classes covered in this chapter are described in Table 18.1.
Table 18.1 File System Access Classes
Class | Description |
File |
A static utility class that exposes many static methods for moving, copying, and deleting files. |
Directory |
A static utility class that exposes many static methods for moving, copying, and deleting directories. |
Path |
A utility class used to manipulate path names. |
FileInfo |
Represents a physical file on disk, and has methods to manipulate this file. For any reading from and writing to the file, a Stream object must be created. |
DirectoryInfo |
Represents a physical directory on disk and has methods to manipulate this directory. |
FileSystemInfo |
Serves as the base class for both FileInfo and DirectoryInfo , making it possible to deal with files and directories at the same time using polymorphism. |
FileSystemWatcher |
The most advanced class you examine in this chapter. It is used to monitor files and directories, and it exposes events that your application can catch when changes occur in these locations. |
You'll also look at the System.IO.Compression
namespace, which enables you to read from and write to compressed files. In particular, you will look at the following two stream classes:
DeflateStream
— Represents a stream in which data is compressed automatically when writing, or uncompressed automatically when reading. Compression is achieved using the Deflate algorithm.GZipStream
— Represents a stream in which data is compressed automatically when writing, or uncompressed automatically when reading. Compression is achieved using the GZIP (GNU Zip) algorithm.The File
and Directory
utility classes expose many static methods for manipulating, surprisingly enough, files and directories. These methods make it possible to move files, query and update attributes, and create FileStream
objects. As you learned in Chapter 8, static methods can be called on classes without having to create instances of them.
Some of the most useful static methods of the File
class are shown in the Table 18.2.
Table 18.2 Static Methods of the File Class
Method | Description |
Copy() |
Copies a file from a source location to a target location. |
Create() |
Creates a file in the specified path. |
Delete() |
Deletes a file. |
Open() |
Returns a FileStream object at the specified path. |
Move() |
Moves a specified file to a new location. You can specify a different name for the file in the new location. |
Some useful static methods of the Directory
class are shown in Table 18.3.
Table 18.3 Static Methods of the Directory Class
Method | Description |
CreateDirectory() |
Creates a directory with the specified path. |
Delete() |
Deletes the specified directory and all the files within it. |
GetDirectories() |
Returns an array of string objects that represent the names of the directories below the specified directory. |
EnumerateDirectories() |
Like GetDirectories() , but returns an IEnumerable< string > collection of directory names. |
GetFiles() |
Returns an array of string objects that represent the names of the files in the specified directory. |
EnumerateFiles() |
Like GetFiles() , but returns an IEnumerable< string > collection of filenames. |
GetFileSystemEntries() |
Returns an array of string objects that represent the names of the files and directories in the specified directory. |
EnumerateFileSystemEntries() |
Like GetFileSystemEntries() , but returns an IEnumerable< string > collection of file and directory names. |
Move() |
Moves the specified directory to a new location. You can specify a new name for the folder in the new location. |
The three Enumerate
Xxx
()
methods provide better performance than their Get
Xxx
()
counterparts when a large amount of files or directories exist.
Unlike the File
class, the FileInfo
class is not static and does not have static methods. This class is useful only when instantiated. A FileInfo
object represents a file on a disk or a network location, and you can create one by supplying a path to a file:
FileInfo aFile = new FileInfo(@"C:Log.txt");
You can also pass the name of a directory to the FileInfo
constructor, although in practical terms that isn't particularly useful. Doing this causes the base class of FileInfo
, which is FileSystemInfo
, to be initialized with all the directory information, but none of the FileInfo
methods or properties relating specifically to files will work.
Many of the methods exposed by the FileInfo
class are similar to those of the File
class, but because File
is a static class, it requires a string parameter that specifies the file location for every method call. Therefore, the following calls do the same thing:
FileInfo aFile = new FileInfo("Data.txt");
if (aFile.Exists)
WriteLine("File Exists");
if (File.Exists("Data.txt"))
WriteLine("File Exists");
In this code, a check is made to see whether the file Data.txt
exists. Note that no directory information is specified here, which means that the current working directory is the only location examined. This directory is the one containing the application that calls this code. You'll look at this in more detail a little later, in the section “Path Names and Relative Paths.”
Most of the FileInfo
methods mirror the File
methods in this manner. In most cases it doesn't matter which technique you use, although the following criteria can help you to decide which is more appropriate:
File
class if you are making only a single method call — the single call will be faster because the .NET Framework won't have to go through the process of instantiating a new object and then calling the method.FileInfo
object and use its methods — this saves time because the object will already be referencing the correct file on the file system, whereas the static class has to find it every time.The FileInfo
class also exposes properties relating to the underlying file, some of which can be manipulated to update the file. Many of these properties are inherited from FileSystemInfo
, and thus apply to both the FileInfo
and DirectoryInfo
classes. The properties of FileSystemInfo
are shown in Table 18.4.
Table 18.4 FileSystemInfo Properties
Property | Description |
Attributes |
Gets or sets the attributes of the current file or directory, using the FileAttributes enumeration. |
CreationTime , CreationTimeUtc |
Gets or sets the creation date and time of the current file, available in coordinated universal time (UTC) and non-UTC versions. |
Extension |
Retrieves the extension of the file. This property is read-only. |
Exists |
Determines whether a file exists. This is a read-only abstract property, and is overridden in FileInfo and DirectoryInfo . |
FullName |
Retrieves the full path of the file. This property is read-only. |
LastAccessTime , LastAccessTimeUtc |
Gets or sets the date and time that the current file was last accessed, available in UTC and non-UTC versions. |
LastWriteTime , LastWriteTimeUtc |
Gets or sets the date and time that the current file was last written to, available in UTC and non-UTC versions. |
Name |
Retrieves the full path of the file. This is a read-only abstract property, and is overridden in FileInfo and DirectoryInfo . |
The properties specific to FileInfo
are shown in Table 18.5.
Table 18.5 FileInfo Properties
Property | Description |
Directory |
Retrieves a DirectoryInfo object representing the directory containing the current file. This property is read-only. |
DirectoryName |
Returns the path to the file's directory. This property is read-only. |
IsReadOnly |
Shortcut to the read-only attribute of the file. This property is also accessible via Attributes . |
Length |
Gets the size of the file in bytes, returned as a long value. This property is read-only. |
The DirectoryInfo
class works exactly like the FileInfo
class. It is an instantiated object that represents a single directory on a machine. Like the FileInfo
class, many of the method calls are duplicated across Directory
and DirectoryInfo
. The guidelines for choosing whether to use the methods of File
or FileInfo
also apply to DirectoryInfo
methods:
Directory
class.DirectoryInfo
object.The DirectoryInfo
class inherits most of its properties from FileSystemInfo
, as does FileInfo
, although these properties operate on directories instead of files. There are also two DirectoryInfo
-specific properties, shown in Table 18.6.
Table 18.6 Properties Unique to the DirectoryInfo Class
Property | Description |
Parent |
Retrieves a DirectoryInfo object representing the directory containing the current directory. This property is read-only. |
Root |
Retrieves a DirectoryInfo object representing the root directory of the current volume — for example, the C: directory. This property is read-only. |
When specifying a path name in .NET code, you can use absolute or relative path names. An absolute path name explicitly specifies a file or directory from a known location — such as the C:
drive. An example of this is C:WorkLogFile.txt
— this path defines exactly where the file is, with no ambiguity.
Relative path names are relative to a starting location. By using relative path names, no drive or known location needs to be specified. You saw this earlier, where the current working directory was the starting point, which is the default behavior for relative path names. For example, if your application is running in the C:DevelopmentFileDemo
directory and uses the relative path LogFile
.txt
, the file references would be C:DevelopmentFileDemoLogFile.txt
. To move “up” a directory, the ..
string is used. Thus, in the same application, the path ..Log.txt
points to the file C:DevelopmentLog.txt
.
As shown earlier, the working directory is initially set to the directory in which your application is running. When you are developing with Visual Studio, this means the application is several directories beneath the project folder you created. It is usually located in ProjectName
inDebug
. To access a file in the root folder of the project, then, you have to move up two directories with ....
. You will see this happen often throughout the chapter.
Should you need to, you can determine the working directory by using Directory.GetCurrentDirectory()
, or you can set it to a new path by using Directory.SetCurrentDirectory()
.
All input and output in the .NET Framework involves the use of streams. A stream is an abstract representation of a serial device. A serial device is something that stores and/or accesses data in a linear manner, that is, one byte at a time, sequentially. This device can be a disk file, a network channel, a memory location, or any other object that supports linear reading, writing, or both. By keeping the device abstract, the underlying destination/source of the stream can be hidden. This level of abstraction enables code reuse, and enables you to write more generic routines because you don't have to worry about the specifics of how data transfer actually occurs. Therefore, similar code can be transferred and reused when the application is reading from a file input stream, a network input stream, or any other kind of stream. Because you can ignore the physical mechanics of each device, you don't need to worry about, for example, hard disk heads or memory allocation when dealing with a file stream.
A stream can represent almost any source such as a keyboard, a physical disk file, a network location, a printer, or even another program, but this chapter focuses on reading and writing disk files. The concepts applied to reading/writing disk files apply to most devices, so you'll gain a basic understanding of streams and learn a proven approach that can be applied to many situations.
The classes for using streams are contained in the same System.IO
namespace along with the File
and Directory
classes. These classes are listed in Table 18.7.
Table 18.7 Stream Classes
Class | Description |
FileStream |
Represents a file that can be written to, read from, or both. This file can be written to and read from asynchronously or synchronously. |
StreamReader |
Reads character data from a stream and can be created by using a FileStream as a base. |
StreamWriter |
Writes character data to a stream and can be created by using a FileStream as a base. |
Let's look now at how to use each of these classes.
The FileStream
object represents a stream pointing to a file on a disk or a network path. Although the class does expose methods for reading and writing bytes from and to the files, most often you will use a StreamReader
or StreamWriter
to perform these functions. That's because the FileStream
class operates on bytes and byte arrays, whereas the Stream
classes operate on character data. Character data is easier to work with, but certain operations, such as random file access (access to data at some point in the middle of a file), can be performed only by a FileStream
object. You'll learn more about this later in the chapter.
There are several ways to create a FileStream
object. The constructor has many different overloads, but the simplest takes just two arguments: the filename and a FileMode
enumeration value:
FileStream aFile = new FileStream(filename, FileMode.<Member
>);
The FileMode
enumeration has several members that specify how the file is opened or created. You'll see the possibilities shortly. Another commonly used constructor is as follows:
FileStream aFile =
new FileStream(filename, FileMode.<Member
>, FileAccess.<Member
>);
The third parameter is a member of the FileAccess
enumeration and is a way of specifying the purpose of the stream. The members of the FileAccess
enumeration are shown in Table 18.8.
Table 18.8 FileAccess Enumeration Members
Member | Description |
Read |
Opens the file for reading only |
Write |
Opens the file for writing only |
ReadWrite |
Opens the file for reading or writing |
Attempting to perform an action other than that specified by the FileAccess
enumeration member will result in an exception being thrown. This property is often used as a way to vary user access to the file based on the user's authorization level.
In the version of the FileStream
constructor that doesn't use a FileAccess
enumeration parameter, the default value is used, which is FileAccess.ReadWrite
.
The FileMode
enumeration members are shown in Table 18.9. What actually happens when each of these values is used depends on whether the filename specified refers to an existing file. Note that the entries in this table refer to the position in the file that the stream points to when it is created, a topic you'll learn more about in the next section. Unless otherwise stated, the stream points to the beginning of a file.
Table 18.9 FileMode Enumeration Members
Member | File Exists Behavior | No File Exists Behavior |
Append |
The file is opened, with the stream positioned at the end of the file. Can be used only in conjunction with FileAccess.Write . |
A new file is created. Can be used only in conjunction with FileAccess.Write . |
Create |
The file is destroyed, and a new file is created in its place. | A new file is created. |
CreateNew |
An exception is thrown. | A new file is created. |
Open |
The file is opened, with the stream positioned at the beginning of the file. | An exception is thrown. |
OpenOrCreate |
The file is opened, with the stream positioned at the beginning of the file. | A new file is created. |
Truncate |
The file is opened and erased. The stream is positioned at the beginning of the file. The original file creation date is retained. | An exception is thrown. |
Both the File
and FileInfo
classes expose OpenRead()
and OpenWrite()
methods that make it easier to create FileStream
objects. The first opens the file for read-only access, and the second allows write-only access. These methods provide shortcuts, so you do not have to provide all the information required in the form of parameters to the FileStream
constructor. For example, the following line of code opens the Data.txt
file for read-only access:
FileStream aFile = File.OpenRead("Data.txt");
The following code performs the same function:
FileInfo aFileInfo = new FileInfo("Data.txt");
FileStream aFile = aFileInfo.OpenRead();
The FileStream
class maintains an internal file pointer that points to the location within the file where the next read or write operation will occur. In most cases, when a file is opened, it points to the beginning of the file, but this pointer can be modified. This enables an application to read or write anywhere within the file, which in turn enables random access to a file and the capability to jump directly to a specific location in the file. This can save a lot of time when dealing with very large files because you can instantly move to the location you want.
The method that implements this functionality is the Seek()
method, which takes two parameters. The first parameter specifies how far to move the file pointer, in bytes. The second parameter specifies where to start counting from, in the form of a value from the SeekOrigin
enumeration. The SeekOrigin
enumeration contains three values: Begin
, Current
, and End
.
For example, the following line would move the file pointer to the eighth byte in the file, starting from the very first byte in the file:
aFile.Seek(8, SeekOrigin.Begin);
The following line would move the file pointer two bytes forward, starting from the current position. If this were executed directly after the previous line, then the file pointer would now point to the tenth byte in the file:
aFile.Seek(2, SeekOrigin.Current);
When you read from or write to a file, the file pointer changes as well. After you have read 10 bytes, the file pointer will point to the byte after the tenth byte read.
You can also specify negative seek positions, which could be combined with the SeekOrigin.End
enumeration value to seek near the end of the file. The following seeks to the fifth byte from the end of the file:
aFile.Seek(-5, SeekOrigin.End);
Files accessed in this manner are sometimes referred to as random access files because an application can access any position within the file. The StreamReader
and StreamWriter
classes described later access files sequentially and do not allow you to manipulate the file pointer in this way.
Reading data using the FileStream
class is not as easy as using the StreamReader
class, which you will look at later in this chapter. That's because the FileStream
class deals exclusively with raw bytes. Working in raw bytes makes the FileStream
class useful for any kind of data file, not just text files. By reading byte data, the FileStream
object can be used to read files such as images or sound files. The cost of this flexibility is that you cannot use a FileStream
to read data directly into a string as you can with the StreamReader
class. However, several conversion classes make it fairly easy to convert byte arrays into character arrays, and vice versa.
The FileStream.Read()
method is the primary means to access data from a file that a FileStream
object points to. This method reads the data from a file and then writes this data into a byte
array. There are three parameters, the first being a byte
array passed in to accept data from the FileStream
object. The second parameter is the position in the byte
array to begin writing data to — this is normally zero, to begin writing data from the file at the beginning of the array. The last parameter specifies how many bytes to read from the file.
The following Try It Out demonstrates reading data from a random access file. The file you will read from is actually the class file you create for the example.
The process for writing data to a random access file is very similar; a byte array must be created. The easiest way to do this is to first build the character array you want to write to the file. Next, use the Encoder
object to convert it to a byte array, very much as you used the Decoder
object. Last, call the Write()
method to send the array to the file.
Here's a simple example to demonstrate how this is done.
Working with arrays of bytes is not most people's idea of fun — having worked with the FileStream
object, you might be wondering whether there is an easier way. Fear not, for once you have a FileStream
object, you will usually create a StreamWriter
or StreamReader
and use its methods to manipulate the file. If you don't need the capability to change the file pointer to any arbitrary position, these classes make working with files much easier.
The StreamWriter
class enables you to write characters and strings to a file, with the class handling the underlying conversions and writing to the FileStream
object for you.
There are many ways to create a StreamWriter
object. If you already have a FileStream
object, then you can use it to create a StreamWriter
:
FileStream aFile = new FileStream("Log.txt", FileMode.CreateNew);
StreamWriter sw = new StreamWriter(aFile);
A StreamWriter
object can also be created directly from a file:
StreamWriter sw = new StreamWriter("Log.txt", true);
This constructor takes the filename and a Boolean value that specifies whether to append to the file or create a new one:
false
, then a new file is created or the existing file is truncated and then opened.true
, then the file is opened and the data is retained. If there is no file, then a new one is created.Unlike creating a FileStream
object, creating a StreamWriter
does not provide you with a similar range of options — other than the Boolean value to append or create a new file, you have no option for specifying the FileMode
property as you did with the FileStream
class. Nor do you have an option to set the FileAccess
property, so you will always have read/write privileges to the file. To use any of the advanced parameters, you must first specify them in the FileStream
constructor and then create a StreamWriter
from the FileStream
object, as you do in the following Try It Out.
Input streams are used to read data from an external source. Often, this will be a file on a disk or network location, but remember that this source could be almost anything that can send data, such as a network application or even the Console.
The StreamReader
class is the one that you will be using to read data from files. Like the StreamWriter
class, this is a generic class that can be used with any stream. In the next Try It Out, you again construct it around a FileStream
object so that it points to the correct file.
StreamReader
objects are created in much the same way as StreamWriter
objects. The most common way to create one is to use a previously created FileStream
object:
FileStream aFile = new FileStream("Log.txt", FileMode.Open);
StreamReader sr = new StreamReader(aFile);
Like StreamWriter
, the StreamReader
class can be created directly from a string containing the path to a particular file:
StreamReader sr = new StreamReader("Log.txt");
The ReadLine()
method is not the only way you can access data in a file. The StreamReader
class has many methods for reading data.
The simplest of the reading methods is Read()
. It returns the next character from the stream as a positive integer value or a -1
if it has reached the end. This value can be converted into a character by using the Convert
utility class. In the preceding example, the main parts of the program could be rewritten as follows:
StreamReader sr = new StreamReader(aFile);
int charCode;
charCode = sr.Read();
while(charCode != -1)
{
Write(Convert.ToChar(charCode));
charCode = sr.Read();
}
sr.Close();
A very convenient method to use with smaller files is the ReadToEnd()
method. It reads the entire file and returns it as a string. In this case, the earlier application could be simplified to the following:
StreamReader sr = new StreamReader(aFile);
line = sr.ReadToEnd();
WriteLine(line);
sr.Close();
Although this might seem easy and convenient, be careful. By reading all the data into a string object, you are forcing the data in the file to exist in memory. Depending on the size of the data file, this can be prohibitive. If the data file is extremely large, then it is better to leave the data in the file and access it with the methods of the StreamReader
.
Another way to deal with large files, which was introduced in .NET 4, is to use the static File.ReadLines()
method. There are, in fact, several static methods of File
that you can use to simplify reading and writing file data, but this one is particularly interesting in that it returns an IEnumerable<
string>
collection. You can iterate through the strings in this collection to read the file one line at a time. Using this method, you can rewrite the previous example as follows:
foreach (string alternativeLine in File.ReadLines("Log.txt"))
WriteLine(alternativeLine);
There are, as you can see, several ways in .NET to achieve the same result — namely, reading data from a file. Choose the technique that suits you best.
Sometimes — for example, when you are performing a lot of file access operations in one go or are working with very large files — reading and writing file system data can be slow. If this is the case, you might want to perform other operations while you wait. This is especially important with desktop applications, where you want your application to remain responsive to users while you are doing work in the background.
To facilitate this, .NET 4.5 introduced asynchronous ways to work with streams. This applies to the FileStream
class, as well as to StreamReader
and StreamWriter
. If you have browsed through the definitions of these classes, you might have noticed some methods that end with the suffix Async
— for example, StreamReader
has a method called ReadLineAsync()
, which is an asynchronous version of ReadLine()
. These methods are designed to be used with the task-based asynchronous programming model.
Asynchronous programming is an advanced technique that isn't covered in detail in this book. However, if asynchronous file system access is something you are interested in doing then this is the place to start. You might also want to read Professional C# 5.0 and .NET 4.5.1 by Christian Nagel, Jay Glynn, and Morgan Skinner (Wrox, 2014) for more details.
Often when dealing with files, quite a lot of space is used up on the hard disk. This is particularly true for graphics and sound files. You've probably come across utilities that enable you to compress and decompress files, which are handy when you want to move them around or e-mail them. The System.IO.Compression
namespace contains classes that enable you to compress files from your code, using either the GZIP or Deflate algorithm — both of which are publicly available and free for anyone to use.
There is a little bit more to compressing files than just compressing them, though. You've probably seen how commercial applications enable multiple files to be placed in a single compressed file, often called an archive. There are classes in the System.IO.Compression
namespace that enable similar functionality. However, to keep things simple for this book you'll just look at one scenario: saving text data to a compressed file. You are unlikely to be able to access this file in an external utility, but the file will be much smaller than its uncompressed equivalent!
The two compression stream classes in the System.IO.Compression
namespace that you'll look at here, DeflateStream
and GZipStream
, work very similarly. In both cases, you initialize them with an existing stream, which, in the case of files, will be a FileStream
object. After this you can use them with StreamReader
and StreamWriter
just like any other stream. All you need to specify in addition to that is whether the stream will be used for compression (saving files) or decompression (loading files) so that the class knows what to do with the data that passes through it. This is best illustrated with the following example.
Sometimes an application must do more than just read and write files to the file system. For example, it might be important to know when files or directories are being modified. The .NET Framework has made it easy to create custom applications that do just that.
The class that helps you to do this is the FileSystemWatcher
class. It exposes several events that your application can catch. This enables your application to respond to file system events.
The basic procedure for using the FileSystemWatcher
is simple. First, you must set a handful of properties, which specify where to monitor, what to monitor, and when it should raise the event that your application will handle. Then you give it the addresses of your custom event handlers, so that it can call these when significant events occur. Finally, you turn it on and wait for the events.
The properties that must be set before a FileSystemWatcher
object is enabled are shown in Table 18.10.
Table 18.10 FileSystemWatcher Properties
Property | Description |
Path |
Must be set to the file location or directory to monitor. |
NotifyFilter |
A combination of NotifyFilters enumeration values that specify what to watch for within the monitored files. These represent properties of the file or folders being monitored. If any of the specified properties change, then an event is raised. The possible enumeration values are Attributes , CreationTime , DirectoryName , FileName , LastAccess , LastWrite , Security , and Size . Note that these can be combined using the binary OR operator. |
Filter |
A filter specifying which files to monitor — for example, *.txt . |
Once these are set, you must write event handlers for four events: Changed
, Created
, Deleted
, and Renamed
. As shown in Chapter 13, this is simply a matter of creating your own method and assigning it to the object's event. By assigning your own event handler to these methods, your method will be called when the event is fired. Each event will fire when a file or directory matching the Path
, NotifyFilter
, and Filter
property is modified.
Once you have set the properties and the events, set the EnableRaisingEvents
property to true
to begin the monitoring. In the following Try It Out, you use FileSystemWatcher
in a simple client application to keep tabs on a directory of your choice.
Topic | Key Concepts |
Streams | A stream is an abstract representation of a serial device that you can read from or write to a byte at a time. Files are an example of such a device. There are two types of streams — input and output — for reading from and writing to devices, respectively. |
File classes | There are numerous classes in the .NET Framework that abstract file system access, including File and Directory for dealing with files and directories through static methods, and FileInfo and DirectoryInfo , which can be instantiated to represent specific files and directories. The latter pair of classes is useful when you perform multiple operations on files and directories, as those classes don't require a path for every method call. Typical operations that you can perform on files and directories include interrogating and changing properties, creating, deleting, and copying. |
File paths | File and directory paths can be absolute or relative. An absolute path gives a complete description of a location starting from the root of the drive that contains it; all parent directories are separated from child directories with backslashes. Relative directories are similar, but start from a defined point in the file system, such as the directory where an application is executing (the working directory). To navigate the file system, you often use the .. parent directory alias. |
The FileStream object |
The FileStream object provides access to the contents of a file, for reading and writing purposes. It accesses file data at the byte level, and so is not always the best choice for accessing file data. A FileStream instance maintains a position byte index within a file so that you can navigate through the contents of a file. Accessing a file at any point in this way is known as random access. |
Reading and writing to streams | An easier way to read and write file data is to use the StreamReader and StreamWriter classes in combination with a FileStream . These enable you to read and write character and string data rather than working with bytes. These types expose familiar methods for working with strings, including ReadLine() and WriteLine() . Because they work with string data, these classes make it easy to work with comma-delimited files, which are a common way to represent structured data. |
Compressed files | You can use the DeflateStream and GZipStream compressed stream classes to read and write compressed data from and to files. These classes work with byte data much like FileStream , but as with FileStream you can access data through StreamReader and StreamWriter classes to simplify your code. |
Monitoring the file system | You can use the FileSystemWatcher class to monitor changes to file system data. You can monitor both files and directories, and provide a filter, if required, to modify only those files that have a specific file extension. FileSystemWatcher instances notify you of changes by raising events that you can handle in your code. |