The IOpenable interface

published: Wed, 6-Apr-2005   |   updated: Thu, 27-Oct-2005

There seems to be a common pattern in some of the stuff I've been reviewing recently at work. In essence, there are several classes that implement some behavior such that an instance of one of the classes can be "opened" and then "closed" after use, after which it is no longer used. Also each class encapsulates a non-memory resource and hence needs a finalizer. A prime example of the resources being protected in this manner is a file, but others include sockets and the like.

The problem was that some classes I looked at defined a finalizer but didn't implement IDisposable. Because of this, although the resource was being closed explicitly in the main code, the object hung around until finalization time.

So, this behavior needed to change. What I wanted to do was something like this (can you spell TDD?):

MyFile f = new MyFile("example.txt");
using (f.Open()) {
  // do work with f, now it's open
} // f should be closed automatically here

There are several things to note here. First I'm assuming the constructor doesn't open the resource. This goes along with my current principle that I should avoid throwing exceptions from constructors. Since opening a resource could throw an exception (the resource is not available, for example), I'd rather use an explicit Open() method instead.

(Note that I'm well aware that there are obviously some exceptions that could be thrown from a constructor that I'll have no control over, out of memory for example. All I'm saying is that we should try and avoid explicitly throwing an exception. Why? Well, consider what happens in the following code:

MyObj o = new MyObj();

If no exception is thrown, the flow goes something like this. The run- time allocates memory for the object and makes sure all fields are set to their default values (zero, false, null, etc). Then the run-time starts executing the constructor for the object (which may in turn call the constructors for its ancestors). Finally, the run-time sets the variable o to the newly constructed object.

And if an exception is thrown in the midst of constructing the object? Well, simple really: the final assignment of the newly constructed object to the variable o will not take place. The newly constructed but-not-quite-fully-initialized object is orphaned -- there will be no reference to it at all -- and will be collected in the next pass of the garbage collector. Sounds good, eh? So who cares if the constructor throws?

But if the class has a finalizer then the object stays around in the finalization queue until such time that the garbage collector gets round to running the finalizers and finally disposing of the objects on the queue. And, of course, if the orphaned object has references to other objects within it, those will also not get collected until the finalizer runs.

All in all, if a constructor throws, it may cause zombie objects.)

The code also illustrates a principle that a constructor shouldn't do a lot of work (creating the object should be "light-weight" in other words), and instead the lion's share of the work should be done explicitly by calling a method on the new object.

I suppose this latter principle is a bit like the situation of making sure that reading a property doesn't go ahead and do a lot of work (such as access the value across the network, say). The reason is that, to a developer, reading a property looks like a quick thing to do, whereas calling a method (getPropertyValue() say) does look as if there may be some work involved. Developers have this perception (rightly or wrongly) that properties are just simple wrappers around an internal field, whereas a method seems to have to break into a sweat to do the work. So, for example, developers are more likely to cache a returned value from a method in a local variable and use the local variable thereafter, than to cache a property value in a similar manner, reasoning that there will be a small performance boost by doing so. And all because of the perception that properties execute quickly and methods do not.

Another thing to note about the above code is that Open() must return an IDisposable instance, otherwise the compiler won't compile the using statement. Also Dispose(), which is going to be automatically called by the using statement, must do exactly the same as Close() does, no more no less.

This is the code I wrote on the way to solving this test case:

  interface IOpenable : IDisposable {
    IDisposable Open();
    void Close();
  }

  class OpenableBase : IOpenable {
    private bool opened;
    private bool disposed;

    ~OpenableBase() {
      Dispose(false);
    }

    public IDisposable Open() {
      internalOpen();
      opened = true;
      return this;
    }

    public void Close() {
      Dispose(true);
    }

    private void Dispose(bool disposing) {
      if (!disposed) {
        if (opened)
          internalClose(!disposing);
        if (disposing)
          GC.SuppressFinalize(this);
        disposed = true;
      }
    }

    public void Dispose() {
      Dispose(true);
    }

    protected virtual void internalOpen() {
    }

    protected virtual void internalClose(bool inFinalizer) {
    }
  }

As you can see I first declared an interface, IOpenable, to describe objects that can be opened and closed. This interface descends from IDisposable and also declares two new methods: Open() and Close(). Note that Open() returns an IDisposable instance so that it can be used as the target in a using statement.

I then defined a base class that incorporated the basic functionality. Since I'm assuming that a non-memory resource is going to be encapsulated, there's a finalizer and the usual Dispose()/Dispose(bool) pattern for the IDisposable implementation. Close() executes exactly the same code as Dispose(), that is, Dispose(true), so that Close() and Dispose() are interchangeable (sometimes the code might read better when calling Close() than Dispose() for example). The extensibility is provided by a pair of protected virtual methods, internalOpen() and internalClose(). Finally, the implementation of Open() returns this so that using statements will work correctly and close the correct instance.

Finally, here's a simple class (that doesn't do much!) that descends from OpenableBase and that finally satisfies the code above:

  class MyFile : OpenableBase {
    private string fileName;
    private FileStream fs;

    public MyFile(string fileName) {
      this.fileName = fileName;
    }

    protected override void internalClose(bool inFinalizer) {
      fs.Close();
    }

    protected override void internalOpen() {
      fs = new FileStream(fileName, FileMode.Create);
    }
  }