Filesystem I/O

I covered the basics of opening and reading a file through the File and FileInfo objects in Chapter 2. In this section, I’ll focus on writing to a file using the same objects.

To begin with, File has a Create( ) method. This method takes a filename as a parameter and returns a FileStream, so the most basic creation and writing to a file is fairly intuitive. Stream and its subclasses implement a variety of Write( ) methods, including one that writes an array of bytes to the Stream. The following code snippet creates a file named myfile.txt and writes the text .NET & XML to it:

byte [ ] buffer = new byte [ ] {46,78,69,84,32,38,32,88,77,76};
string filename = "myfile.txt";

FileStream stream;
stream = File.Create(filename);
stream.Write(buffer,0,buffer.Length);

Tip

That byte array is an awkward way to write a string to a Stream; ordinarily, you wouldn’t hardcode an array of bytes like that. I’ll show you a more typical way of encoding a string as a byte array in a moment.

If the file already exists, the previous code overwrites the files’s current contents. You may not want to do that in practice; you may prefer to append to the file if it already exists. You can handle this very easily in .NET in several different ways. This snippet shows one way, with the changes highlighted:

byte [ ] buffer = new byte [ ] {46,78,69,84,32,38,32,88,77,76};
string filename = "myfile.txt";

FileStream stream;
if (File.Exists(filename)) {
  // it already exists, let's append to it
  stream = File.OpenWrite(filename);
  stream.Seek(0,SeekOrigin.End);
} else {
  // it does not exist, let's create it
  stream = File.Create(filename);
}

stream.Write(buffer,0,buffer.Length);

SeekOrigin is an enumeration in the System.IO namespace that indicates where the Seek( ) method should seek from. In this code, I’m seeking 0 bytes from the end, but you could also seek from the beginning of the Stream (SeekOrigin.Begin) or from the current position (SeekOrigin.Current).

File access and permissions

There are several other ways to open a file for writing. For example, this snippet shows several changes from the previous one. The changes are highlighted:

byte [ ] buffer = new byte [ ] {46,78,69,84,32,38,32,88,77,76};
string filename = "myfile.txt";

FileStream stream;
FileMode fileMode;
if (File.Exists(filename)) {
  // it already exists, let's append to it
  fileMode = FileMode.Append;
} else {
  // it does not exist, let's create it
  fileMode = FileMode.CreateNew;
}

stream = File.Open(filename,fileMode,FileAccess.Write,FileShare.None);
stream.Write(buffer,0,buffer.Length);

The File.Open( ) method has several overloads with additional parameters. The FileMode enumeration specifies what operations the file is to be opened for. Table 3-1 lists the FileMode enumerations.

Table 3-1. FileMode values

Value

Description

CreateNew

A new file will be created. If it already exists, an IOException is thrown.

Create

A file will be created, or, if it already exists, truncated and overwritten.

Open

An existing file will be opened. If it does not exist, a FileNotFoundException is thrown.

OpenOrCreate

If the file exists, it will be opened; if it does not, a new file will be created.

Truncate

An existing file will be opened and truncated to zero bytes long. An attempt to read a truncated file will result in an exception being thrown.

Append

If the file exists, it will be opened and data will be written at the end of the file. If the file does not exist, a new file will be created. Any attempt to Seek( ) to a position before the previous end of the file will result in an exception being thrown. An attempt to read from the file will result in an ArgumentException being thrown.

The FileAccess enumeration restricts the operations a program can exercise on the file, once it has been opened. Table 3-2 details the FileAccess enumerations.

Table 3-2. FileAccess values

Value

Description

Read

Data can be read from the file.

Write

Data can be written to the file.

ReadWrite

Data can be read from or written to the file. Equivalent to FileAccess.Read | FileAccess.Write.

FileShare restricts what operations other applications can exercise. Table 3-3 describes the FileShare enumerations.

Table 3-3. FileShare values

Value

Description

None

No other process may access the file as long as this process has it open.

Read

Subsequent requests to read from the file by other processes will succeed, if they have the other appropriate permissions.

Write

Subsequent requests to write to the file by other processes will succeed, if they have the other appropriate permissions.

ReadWrite

Subsequent requests to read from and write to the file by other processes will succeed, if they have the other appropriate permissions (equivalent to FileShare.Read | FileShare.Write).

Inheritable

The file handle is inheritable by child processes. This is not directly supported by Win32.

Tip

Some combinations of FileMode and FileAccess do not make sense and will cause an ArgumentException to be thrown. For example, opening a file with FileMode.Create and FileAccess.Read would mean that you wanted to create a file but then only be allowed to read from it.

Encodings and StreamWriter

Having to create an array of bytes to write with the Stream.Write( ) method is a bit tiresome. Luckily, there are at least two ways to work around this. The first is System.Text.Encoding. This class contains methods to convert strings to and from byte arrays, for a given number of standard encodings, including ASCII, UTF-8, and UTF-16. These encodings are provided as static properties of the Encoding class. Strings in .NET are stored in Unicode—while ASCII characters are each stored in a single byte, Unicode characters are stored in four bytes. The GetBytes( ) method takes a .NET string and returns an array of bytes in the appropriate encoding, suitable for use by Stream.Write( ):

Tip

Unicode is a standard that provides a unique four-byte representation of every character in every alphabet, on any computer operating system and platform. Happily, XML and .NET both use encodings of Unicode by default. For more information about Unicode, visit the Unicode Consortium’s web site at http://www.unicode.org/.

string message = "Hello, world.";
byte [ ] buffer = Encoding.ASCII.GetBytes(message);
string filename = "myfile.txt";

FileStream stream;
FileMode fileMode;
if (File.Exists(filename)) {
  // it already exists, let's append to it
  fileMode = FileMode.Append;
} else {
  // it does not exist, let's create it
  fileMode = FileMode.CreateNew;
}

stream = File.Open(filename,fileMode,FileAccess.Write,FileShare.None);
stream.Write(buffer,0,buffer.Length);

Just to belabor the point, remember that a C# byte is the familiar eight-bit byte, but a C# char is a Unicode character. The encodings defined in the .NET Framework are shown in Table 3-4.

Table 3-4. Supported encodings

Class name

Property name

Description

ASCIIEncoding

Encoding.ASCII

7 bit ASCII, used in the snippet above. Support is required for XML processors.

UnicodeEncoding

Encoding.BigEndianUnicode

Encoding.Unicode

Big-endian Unicode.

Little-endian Unicode.

Encoding.Default

Represents the default encoding for the current system.

UTF7Encoding

Encoding.UTF7

UTF-7.

UTF8Encoding

Encoding.UTF8.

UTF-8. Support is required for XML processors.

In addition, any other encodings are accessible by calling Encoding.GetEncoding( ) and passing the code page or encoding name.

The other way to write a stream of characters is even simpler. A FileStream is a subclass of Stream, which can be used as a parameter to the StreamWriter’s constructor. StreamWriter is analogous to StreamReader, and is a subclass of TextWriter, which is optimized to write textual data to a Stream. The TextWriter’s Write( ) and WriteLine( ) methods take care of the encoding of various datatypes when writing to a Stream:

string textToWrite = "This is the text I want to write to the file.";
string filename = "myfile.txt";

FileStream stream;
FileMode fileMode;
if (File.Exists(filename)) {
  // it already exists, let's append to it
  fileMode = FileMode.Append;
} else {
  // it does not exist, let's create it
  fileMode = FileMode.CreateNew;
}

stream = File.Open(filename,fileMode,FileAccess.Write,FileShare.None);
StreamWriter writer = new StreamWriter(stream);
writer.Write(textToWrite);
writer.Flush( );
writer.Close( );

The last two lines of this code snippet cause the output buffer to be flushed, and the file to be closed. Every time you write to the file and you want the file on disk to reflect the changes immediately, it is important to call Flush( ) on the Stream or StreamWriter. You can also indicate that the contents of the file are to be flushed to disk automatically by setting AutoFlush to true:

StreamWriter writer = new StreamWriter(stream);
writer.AutoFlush = true;        
writer.Write(textToWrite);

When you are completely done with a file, you should call Close( ) on the File or the Stream. If you don’t call Close( ) yourself, the file will be closed when the garbage collector cleans up the method’s local variables. Unfortunately, you don’t know when that will happen, so it’s always best to close files yourself.

Tip

You can close the underlying file by calling Close( ) on the FileStream because, by default, when you instantiate a FileStream, it owns the underlying file. It’s important to remember that the FileStream owns the underlying file handle in case you open several streams on the same file; closing any one of them will cause all of them to be closed.

There’s an even quicker way to append to an existing file. The StreamWriter class has a constructor that takes a filename as a parameter. Since StreamWriter inherits from TextWriter, it implements the IDisposable interface, which allows you to use the using keyword to automatically close the Stream at the end of the using block.

All the code you wrote above could instead be simplified to five lines, if all you want to do is write a quick chunk of text to a file:

string textToWrite = "This is the text I want to write to the file.";
string filename = "myfile.txt";
using (StreamWriter writer = new StreamWriter(filename,true)) {
  writer.Write(textToWrite);
}

The second parameter to the StreamWriter constructor indicates that the text is to be appended to the file if the file already exists.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.247.31