As you work your way through the Windows Runtime (WinRT) APIs in the process of building an app, you begin to notice that there is a plethora of object types and interfaces that all have to do with managing and manipulating piles of data in some manner. Together, they all remind me of this gem of a storage box I saw in a store in San Francisco’s Chinatown (and subsequently bought as a Christmas present for my father-in-law):
To be most specific, here’s the roster I’m referring to:
- File system entities, represented by classes in the Windows.Storage namespace, namely StorageFolder, StorageFile, and their shared base interface, IStorageItem.
- Streams, represented by classes in Windows.Storage.Streams. Here we find FileInputStream, FileOutputStream, FileRandomAccessStream, IInputStream, IOutputStream, IRandomAccessStream, InMemoryRandomAccessStream, InputStreamOverStream, OutputStreamOverStream, RandomAccessStream, and RandomAccessStreamOverStream. (Egads!)
- Buffers, also in Windows.Storage.Streams, limited here to the Buffer class and the IBuffer interface.
My guess is that many developers, upon encountering these object types and interfaces at inconvenient times when you’re really just trying to complete some simple task in an app, can easily become somewhat lost in the jungle, so to speak. And I haven’t found a topic in the documentation that brings all of these together.
Instead of trying to explain every last detail, however, what I think will help most is to answer a few key questions, the answers to which have, for me, made much better sense of the landscape. This makes for a nice series of posts!
Question #1: Why on earth doesn’t the StorageFile class have a close method?
Answer: The very first thing to understand clearly is that StorageFolder and StorageFile represent entities on the file system but not contents of files themselves. (That’s actually what streams are for.)
This is critical part of your mental model that likely needs updating. Ask yourself this: what’s one of the first things we tinker with when learning to code? We play with file I/O–open a file, read its contents, and close the file, thus establishing this basic mental model at a young age. If we’re use the C runtime library for this (OK, so I’m dating myself), we learn about the functions fopen, fread, and fclose. We identify a file with a pathname, get back a file pointer from fopen, use that pointer to read from the file, then close the thing. ‘Nuf said.
So when we come to WinRT, we start off well by seeing this StorageFile class and its openAsync method, which we mentally map to fopen. But then we find that StorageFile lacks methods for reading and closing, and our mental model breaks down. What gives?
The main difference is that where fopen and its friends as synchronous, WinRT is an asynchronous API. In that asynchronous world, the request to open a StorageFile produces some results later on. So technically, the file isn’t actually “open” until those results are delivered.
Those results, to foreshadow the next question, is some kind of stream. That stream is what manages access to the file’s contents, so when you want to read from “the file” ala’ fread you actually read from this stream that’s connected to the file contents. When you want to “close the file” in the way that you think of with fclose, you close and dispose of the stream. This is why the StorageFile object does not have its own close method anywhere.
Here, then, is they key: don’t think of a StorageFile as a thing you open and read. Think of it instead as an abstraction for the pathname that you use to identify the file entity. This is important because many file-like entities actually have no pathname at all. One of the most powerful changes in Windows 8 is the unification of local and cloud-based storage, which means that a StorageFile isn’t necessarily local to the device. Fortunately, StorageFile hides those details, so you (the app developer) can treat local and cloud files identically, as you can “file” content that comes back from other apps and might not have a file entity anywhere in the known universe.
So remember, StorageFiles identify and provide access to some file-like entity, but streams are what deal with the data itself. Opening a “file” means obtaining a stream for the file’s contents, and the existence of that stream is what holds the file open, not the existence of a StorageFile. So you work with file contents through the methods of a stream object, and when you’re done with that stream you close and dispose of it. This then closes the file in the way that you understand.
And just to say, the APIs in the Windows.Storage.FileIO and PathIO classes give you a simplified way to avoid dealing with streams at all. We’ll come back to this in Question #4 (part 4)