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); } }