< Day Day Up > |
The Common Language Runtime (CLR) requires that every class have a constructor a special-purpose method that initializes a class or class instance the first time it is referenced. There are three basic types of constructors: instance, private (a special case of instance), and static. Instance ConstructorSyntax [attributes] [modifiers] <identifier> ( [parameter-list]) [:initializer] { constructor-body} The syntax is the same as that of the method except that it does not have a return data type and adds an initializer option. There are a number of implementation and behavioral differences:
The instance constructor is called as part of creating a class instance. Because a class may contain multiple constructors, the compiler calls the constructor whose signature matches that of the call: Fiber fib1 = new Cotton(); Fiber fib1 = new Cotton("Egyptian"); .NET constructs an object by allocating memory, zeroing out the memory, and calling the instance constructor. The constructor sets the state of the object. Any fields in the class that are not explicitly initialized are set to zero or null, depending on the associated member type. Inheritance and ConstructorsConstructors, unlike methods, are not inherited. It is up to each class to define its own constructor(s) or use the default parameterless constructor. Each constructor must call a constructor in its base class before the first line of the calling constructor is executed. Because C# generates a call to the base class's default constructor automatically, the programmer typically does not bother with this. But there are exceptions. Consider this code in which the base class Apparel defines a constructor: public class Apparel { private string color; private decimal price; // constructor public Apparel(string c, decimal p, string b) { color = c; price = p; } } // class inheriting from Apparel class Coat: Apparel { private string length; public Coat(string c, decimal p, string b, string l) { length = l; // ... other code } } If you try to compile this, you'll get an error stating that "no overload for Apparel takes 0 arguments". Two factors conspire to cause this: first, because Apparel has an explicit constructor, the compiler does not add a default parameterless constructor to its class definition; and second, as part of compiling the constructor in the derived class, the compiler includes a call to the base class's default constructor which in this case does not exist. The solution is either to add a parameterless constructor to Apparel or to include a call in the derived class to the explicit constructor. Let's look at how a constructor can explicitly call a constructor in a base class. Using InitializersThe C# compiler provides the base initializer as a way for a constructor in an inherited class to invoke a constructor in its base class. This can be useful when several classes share common properties that are set by the base class constructor. Listing 3-9 demonstrates how a derived class, Shirt, uses a base initializer to call a constructor in Apparel. Listing 3-9. A Constructor with a Base Initializerusing System; public class Apparel { private string color; private decimal price; private string brand; // Constructor public Apparel(string c,decimal p, string b) { color = c; price = p; brand = b; } public string ItemColor { get {return color;} } // other properties and members go here } public class Shirt: Apparel { private decimal mySleeve; private decimal myCollar; public Shirt (string c, decimal p, string b, decimal sleeve, decimal collar) : base(c,p,b) { mySleeve = sleeve; myCollar = collar; } } public class TestClass { static void Main() { Shirt shirtClass = new Shirt("white", 15.00m, "Arrow", 32.0m, 15.5m); Console.WriteLine(shirtClass.ItemColor); // "white" } } The compiler matches the signature of this initializer with the instance constructor in the base class that has a matching signature. Thus, when an instance of Shirt is created, it automatically calls the constructor in Apparel that has one parameter. This call is made before any code in Shirt is executed. A second version of the initializer, one that uses the keyword this rather than base, also indicates which constructor is to be called when the class is instantiated. However, this refers to a constructor within the class instance, rather than the base class. This form of the initializer is useful for reducing the amount of compiler code generated in a class having multiple constructors and several fields to be initialized. For example, if you examine the generated IL code for the following class, you would find that the fields fiberType and color are defined separately for each constructor. public class Natural { string fiberType = "Generic"; string color = "white"; // Constructors public Natural() { ... } public Natural(string cotton_type) { ... } public Natural(string cotton_type, string color) { ... } } For more efficient code, perform the field initialization in a single constructor and have the other constructors invoke it using the this initializer. public Natural() { // constructor initializes fields fiberType="Generic"; color = "white"; } // Following constructors use this() to call default // constructor before constructor body is executed. public Natural(string cotton_type): this() { ... } public Natural(string cotton_type, string color): this() { ... } Private ConstructorRecall that the private modifier makes a class member inaccessible outside its class. When applied to a class constructor, it prevents outside classes from creating instances of that class. Although somewhat non-intuitive (what good is a class that cannot be instantiated?), this turns out to be a surprisingly powerful feature. Its most obvious use is with classes that provide functionality solely through static methods and fields. A classic example is the System.Math class found in the Framework Class Library. It has two static fields, pi and the e (natural logarithmic base), as well as several methods that return trigonometric values. The methods behave as built-in functions, and there is no reason for a program to create an instance of the math class in order to use them. In the earlier discussion of static methods, we presented a class (refer to Listing 3-5) that performs metric conversions. Listing 3-10 shows this class with the private constructor added. Listing 3-10. Private Constructor Used with Class Containing Static Methodsusing System; class Conversions { // class contains functions to provide metric conversions // static method can only work with static field. static cmPerInch = 2.54; private static double gmPerPound = 455; public static double inchesToMetric(double inches) { return(inches * cmPerInch); } public static double poundsToGrams(double pounds) { return(pounds*gmPerPound); } // Private constructor prevents creating class instance private Conversions() { ... } } Although a simple example, this illustrates a class that does not require instantiation: The methods are static, and there is no state information that would be associated with an instance of the class. A natural question that arises is whether it is better to use the private constructor or an abstract class to prevent instantiation. The answer lies in understanding the differences between the two. First, consider inheritance. Although an abstract class cannot be instantiated, its true purpose is to serve as a base for derived classes (that can be instantiated) to create their own implementation. A class employing a private constructor is not meant to be inherited, nor can it be. Secondly, recall that a private constructor only prevents outside classes from instantiating; it does not prevent an instance of the class from being created within the class itself. The traits of the private constructor can also be applied to managing object creation. Although the private constructor prevents an outside method from instantiating its class, it does allow a public method in the class (sometimes called a factory method) to create an object. This means that a class can create instances of itself, control how the outside world accesses them, and control the number of instances created. This topic is discussed in Chapter 4, "Working with Objects in C#." Static ConstructorAlso known as a class or type constructor, the static constructor is executed after the type is loaded and before any one of the type members is accessed. Its primary purpose is to initialize static class members. This limited role results from the many restrictions that distinguish it from the instance constructor:
Although it does not have a parameter, do not confuse it with a default base constructor, which must be an instance constructor. The following code illustrates the interplay between the static and instance constructors. class BaseClass { private static int callCounter; // Static constructor static BaseClass(){ Console.WriteLine("Static Constructor: "+callCounter); } // Instance constructors public BaseClass() { callCounter+= 1; Console.WriteLine("Instance Constructor: "+callCounter); } // ... Other class operations } This class contains a static initializer, a static constructor, and an instance constructor. Let's look at the sequence of events that occur when the class is instantiated: BaseClass myClass1 = new BaseClass(); BaseClass myClass2 = new BaseClass(); BaseClass myClass2 = new BaseClass(); Output: Static Constructor: 0 Instance Constructor: 1 Instance Constructor: 2 Instance Constructor: 3 The compiler first emits code to initialize the static field to 0; it then executes the static constructor code that displays the initial value of callCounter. Next, the base constructor is executed. It increments the counter and displays its current value, which is now 1. Each time a new instance of BaseClass is created, the counter is incremented. Note that the static constructor is executed only once, no matter how many instances of the class are created. |
< Day Day Up > |