Recipe 3.20. Preventing the Creation of an Only Partially Initialized ObjectProblemYou 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 through various properties and/or methods. SolutionBy 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<T> class will not initialize its logStream field to a StreamWriter object on construction: public class Log<T> where T: System.IO.TextWriter { private T logStream = null; public T 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<T> where T: System.IO.TextWriter { public Log(T logStream) { this.logStream = logStream; } private T logStream = null; public T 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. DiscussionThere is a small problem with not supplying a default constructor. If a class inherits from Log<T> 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<T> : Log<T> where T : System.IO.TextWriter { public EnhancedLog (T logStream) { // Initialize… } } What this means is that Log<T> 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<T> constructor contains an unseen call (this call can be seen using Ildasm) to the default constructor of the Log<T> class. This problem can be solved in one of several ways. First, you could simply add a protected default constructor to the Log<T> class. This would prevent the creation of a Log<T> object using the default constructor, but would allow classes inheriting from Log<T> 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<T> class uses the base keyword to call the parameterized constructor of the base Log<T> class, passing in a StreamWriter object: public class EnhancedLog<T> : Log<T> where T : System.IO.TextWriter { public EnhancedLog (T logStream) : base(logStream) { // Initialize… } } A third way to solve this problem is to make the Log<T> 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<T> class. For many cases, this third solution is not the best one. |