DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.21 Preventing the Creation of an Only Partially Initialized Object

Problem

You need to force a client to use an overloaded constructor, which accepts parameters to fully initialize the object, rather than a default constructor, which may not fully initialize the object. Often a default constructor cannot fully initialize an object since it may not have the necessary information to do it. Using a default constructor, the client is required to perform a multistep process; for instance, create the object and then initialize its fields either through various properties and/or methods.

Solution

By removing the default constructor and strictly using parameterized constructors, the client is forced to provide the necessary initialization parameters during object creation. The following Log class will not initialize its LogStream field to a StreamWriter object on construction:

public class Log
{
    private StreamWriter logStream = null;

    public StreamWriter LogStream
    {
        get {return (logStream);}
        set {logStream = value;}
    }

    // use the LogStream field...
    public void Write(string text)
    {
        logStream.Write(text);
    }
}

The C# compiler will automatically create a default constructor that calls the default constructor of its base class, if you omit the constructor for a class. The following modified class will prevent the default constructor from being created:

public class Log
{
    public Log(StreamWriter logStream)
    {
        this.logStream = logStream;
    }

    private StreamWriter logStream = null;

    public StreamWriter LogStream
    {
        get {return (logStream);}
        set {logStream = value;}
    }

    // use the LogStream field...
    public void Write(string text)
    {
        logStream.Write(text);
    }
}

When a client creates an object from this class, the client is forced to initialize the LogStream field.

Discussion

There is a small problem with not supplying a default constructor. If a class inherits from Log and does not supply a constructor of its own, the C# compiler will produce the rather cryptic error "No overload for method `Log' takes `0' arguments." The following class produces this error:

public class EnhancedLog : Log
{
    public EnhancedLog (string s)
    {
        // Initialize...
    }
}

What this means is that Log does not contain a default constructor. The C# compiler automatically adds a call to the base class's default constructor, if you do not specify otherwise. Therefore, the EnhancedLog constructor contains an unseen call (this call can be seen using Ildasm) to the default constructor of the Log class.

This problem can be solved in one of several ways. First, we could simply add a protected default constructor to the Log class. This would prevent the creation of a Log object using the default constructor, but would allow classes inheriting from Log to do so without problems. A second method is to use the base keyword to direct the constructor to call a particular constructor in the base class. The following EnhancedLog class uses the base keyword to call the parameterized constructor of the base Log class, passing in a StreamWriter object:

public class EnhancedLog : Log
{
    public EnhancedLog (string s) : base(new StreamWriter(@"C:\test.log"))
    {
        // Initialize...
    }
}

A third way to solve this problem is to make the Log class noninheritable by adding the sealed keyword to the class declaration. While this prevents the problem of calling the default constructor, it also prevents others from inheriting from and extending the Log class. For many cases, this third solution is not the best one.

    [ Team LiB ] Previous Section Next Section