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.
|