This is part 3 of a series on patterns for writing safe constructors (and destructors) in C# and Java. Start reading at part 1: Exceptions in Constructors.

As seen in part 2, using the Open / Close pattern decouples object creation and resource allocation safely. However, for this solution to work we need to rely on the user of the object to stick to the correct sequence of actions, or we need to explicitly enforce the correct sequence with invariants.

Static factory methods reduce the potential problems we introduced in part 2. This is achieved by combining object creation and calling the Open method. Many of the problems listed at the end of part 2 simply disappear.

This is what it looks like:

private TwoResources()
{
}

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

public static TwoResources FromFiles(string fileNameA, string fileNameB)
{
    TwoResources instance = null;
    try
    {
        instance = new TwoResources();
        instance.Open(fileNameA, fileNameB);
        return instance;
    }
    catch
    {
        try
        {
            instance.Close();
        }
        catch
        {
            // ignore silently
        }
        // throw Exception from Open method
        throw;
    }
}

The constructor and the Open method are now both private. If someone wants to create an instance of the class TwoResources, then they have to use the static factory method. This means that if someone has an instance of TwoResources, it is guaranteed that both resources have been opened successfully.

Note that you will have to make a decision how to treat exceptions raised when closing the instance in the static factory method (line 24). In this example, these exceptions are simply silenced. On the one hand, this is usually a bad idea. On the other hand, if opening the new TwoResources instance fails, it would be more helpful if the factory method threw an exception that explains the failure rather than an exception that masks the original problem.

Here is how the class is now used:

TwoResources instance = null;
try
{
    instance = TwoResources.FromFiles(FILE_A, FILE_B);
    instance.PerformSomeOperation();
}
catch (Exception ex)
{
    if (instance != null)
    {
        instance.Close();
    }
}

If static factory methods strike you as an odd way of creating objects, then consider that all examples in this series use a static factory method to create stream objects: File.Open. Many APIs contain static factory methods because they are a concise way to create and initialize objects. They also provide more flexibility than constructors. For example, they can have meaningful names.

Conclusion

Objects that allocate and release resources can be tricky to manage. Here are some takeaway points:

  • Think twice before allowing a constructor to throw an exception.
  • Never allocate resources in a constructor (apart from memory, I guess).
  • Consider using Open / Close methods or provide a static constructor.
  • Provide a safe and robust Close method.
  • Consider checking the Open / Close state explicitly in each method.
Advertisements