FileStream Constructors

There are two types of FileStream constructors—those for interop scenarios, and the “normal” ones. The “normal” ones take a string for the file path, while the interop ones require either an IntPtr or a SafeFileHandle. These wrap a Win32 file handle that you have retrieved from somewhere. (If you’re not already using such a thing in your code, you don’t need to use these versions.) We’re not going to cover the interop scenarios here.

If you look at the list of constructors, the first thing you’ll notice is that quite a few of them duplicate the various permutations of FileShare, FileAccess, and FileMode overloads we had on File.Open.

You’ll also notice equivalents with one extra int parameter. This allows you to provide a hint for the system about the size of the internal buffer you’d like the stream to use. Let’s look at buffering in more detail.

Many streams provide buffering. This means that when you read and write, they actually use an intermediate in-memory buffer. When writing, they may store your data in an internal buffer, before periodically flushing the data to the actual output device. Similarly, when you read, they might read ahead a whole buffer full of data, and then return to you only the particular bit you need. In both cases, buffering aims to reduce the number of I/O operations—it means you can read or write data in relatively small increments without incurring the full cost of an operating system API call every time.

There are many layers of buffering for a typical storage device. There might be some memory buffering on the actual device itself (many hard disks do this, for example), the filesystem might be buffered (NTFS always does read buffering, and on a client operating system it’s typically write-buffered, although this can be turned off, and is off by default for the server configurations of Windows). The .NET Framework provides stream buffering, and you can implement your own buffers (as we did in our example earlier).

These buffers are generally put in place for performance reasons. Although the default buffer sizes are chosen for a reasonable trade-off between performance and robustness, for an I/O-intensive application, you may need to hand-tune this using the appropriate constructors on FileStream.

Even if you don’t need to tune performance, you still need to be aware of buffering for robustness reasons. If either the process or the OS crashes before the buffers are written out to the physical disk, you run the risk of data loss (hence the reason write buffering is typically disabled on the server). If you’re writing frequently to a Stream or StreamWriter, the .NET Framework will flush the write buffers periodically. It also ensures that everything is properly flushed when the stream is closed. However, if you just stop writing data but you leave the stream open, there’s a good chance data will hang around in memory for a long time without getting written out, at which point data loss starts to become more likely.

In general, you should close files as early as possible, but sometimes you’ll want to keep a file open for a long time, yet still ensure that particular pieces of data get written out. If you need to control that yourself, you can call Flush. This is particularly useful if you have multiple threads of execution accessing the same stream. You can synchronize writes and ensure that they are flushed to disk before the next worker gets in and messes things up! Later in this chapter, we’ll see an example where explicit flushing is extremely important.

Another parameter we can set in the constructor is the FileSystemRights. We used this type earlier in the chapter to set filesystem permissions. FileStream lets us set these directly when we create a file using the appropriate constructor. Similarly, we can also specify an instance of a FileSecurity object to further control the permissions on the underlying file.

Finally, we can optionally pass another enumeration to the FileStream constructor, FileOptions, which contains some advanced filesystem options. They are enumerated in Table 11-9. This is a flags-style enumeration, so you can combine these values.

The last option, Asynchronous, deserves a section all to itself.