This is part 2 of a series on patterns for writing safe constructors (and destructors) in C# and Java.

As seen in Part 1, we can have a problem if a constructor throws an exception after allocating a resource. There is no trivial way of releasing the resource after the constructor bails out. Part 2 discusses the most common pattern to avoid this situation: moving the allocation of resources into an Open() method.

To refresh your mind, here is the contentious class definition from Part 1:

class TwoResources
{
    private Stream fileA;
    private Stream fileB;

    public TwoResources(string fileNameA, string fileNameB)
    {
        fileA = File.Open(fileNameA, FileMode.Open);
        fileB = File.Open(fileNameB, FileMode.Open);
    }
    ... some methods
}

There is a problem with this approach if the constructor successfully opens the first file, but fails to open the second file. The constructor will throw an exception, leaving the caller with no reference to the allocated resource and no possibility of de-allocating it.

The Basic Idea

The Open/Close pattern is a two-step approach that first creates the object safely, and then only allocates resources once the caller has a reference to the object:

class TwoResources
{
    private Stream fileA;
    private Stream fileB;

    public TwoResources()
    {
    }

    public void Open(string fileNameA, string fileNameB)
    {
        fileA = File.Open(fileNameA, FileMode.Open);
        fileB = File.Open(fileNameB, FileMode.Open);
    }

    public void Close()
    {
        if (fileA != null)
        {
            fileA.Close();
        }
        if (fileB != null)
        {
            fileB.Close();
        }
    }
    ... some methods
}

The TwoResources class can now be used in the usual way:

TwoResources instance = null;
try
{
    instance = new TwoResources();
    instance.Open(FILE_A, FILE_B);
    instance.SomeOperation();
}
catch (Exception ex)
{
    // handle exception
}
finally
{
    if(instance != null)
    {
        instance.Close();
    }
}

Problem solved? Mostly. There are still two problems to solve for this approach: safe resource de-allocation and safe object behaviour.

Safe Resource De-allocation

Having a look at the Close() method as implemented above, there is an obvious problem if closing fileA throws an exception. In that case, fileB would not be closed. Here is a more complete implementation:

public void Close()
{
    try
    {
        if (fileA != null)
        {
            fileA.Close();
        }
    }
    finally
    {
        fileA = null;
        try
        {
            if (fileB != null)
            {
                fileB.Close();
            }
        }
        finally
        {
            fileB = null;
        }
    }
}

Ridiculous and hard to read? Yes. But this method behaves as expected. Both fileA and fileB are closed (or at least it is attempted to close both), and it is guaranteed that both fileA and fileB are set to null when the method returns. See the next section why this is important.

Arguably, it is a good idea to follow best practice and call the Close() method from the destructor:

~TwoResources()
{
    Close();
}

This is good defensive programming, but on the other hand it can also mask programming errors. If your code uses a logging framework, it might be a good idea to at least log at warning level if the destructor finds that the object has not been closed yet.

Maintaining Consistent Object Behaviour

You thought the simple example from Part 1 has already been transformed into something complicated enough? Prepare to be amazed.

By using the Open / Close pattern, you are establishing a sequence protocol that any user of your class must follow. The basic sequence is obviously this:

  • Create instance
  • Open
  • Use instance
  • Close

But this is as far as the obvious goes. Can you re-use an instance and call Open again? Do you have to call Close if Open failed?

You might have to consider the following, especially if you expect your classes to be used by third parties:

  • Do you need to enforce the proper sequence with invariants?
  • What is the proper sequence and what are the edge cases?

One example of enforcing the proper protocol is to throw a meaningful exception if the user tries to call an operation on an object that has not been opened properly:

public bool IsOpen
{
    get
    {
        return (fileA != null) && (fileB != null);
    }
}

public void SomeOperation()
{
    if (!IsOpen)
    {
        throw new InvalidOperationException("TwoResources is not open");
    }
    // .. perform operation
}

This is in principle what the Stream implementations in C# and Java are doing as well. The IsOpen property is an example of why it is a good idea to set fileA and fileB to null in the Close() method.

A more complicated alternative is the introduction of an explicit state variable. And this leads us to the question of what the supported sequence should be. Consider the following questions:

  • What happens if Close is called before Open?
  • What happens if any method is called before Open?
  • What happens if Open is called after Close?
  • What happens if any method is called after Close?
  • What happens if Close is called twice?
  • Does Close need to be called after Open failed?

Whatever sequence you design and whether you explicitly enforce it or not: make sure that the behaviour is consistent. One example of this is that you should not punish the user for calling Close after a failed Open. This would break the try/catch/finally model. This actually happens for the otherwise great .Net wrapper for SQLite System.Data.SQLite when you try to open a non-sqlite file.

In conclusion, the Open / Close pattern solves the initial problem: resources can safely be de-allocated. But while you can expect that most developers (as users of your class) will be able to use the Open / Close pattern correctly, you have to consider what the proper sequence of actions is and whether to enforce it.

Wouldn’t it be nice to remove the need for at least some of the state management and thus eliminate potential errors? Tune in for part 3: Static Factory Methods.

Advertisements