Instance Constructors

   


You have already briefly been introduced to constructors in Chapter 5, "Your First Object-Oriented C# Program," and since then you have seen simple implementations of them in several source code examples. The following section elaborates on this earlier introduction and looks at why constructors are needed and how they are used.

Why Do We Need Instance Constructors?

When an object is created, the underlying computer memory representing its instance variables are allocated as part of this process. But just after this allocation procedure (and before any automatic or manual initializations have taken place), the memory and, hence, the instance variables only holds arbitrary garbage left over from last time this memory was used. Using this garbage as part of a computation can lead to detrimental results. Consider the following assignment statement:

 distance = time * speed; 

It is acceptable for distance to contain garbage before this assignment because it will be substituted by the result of the expression on the right side. However, if either time or speed contains garbage, distance will also be assigned garbage. As you can imagine, non-initialized instance variables can easily cause havoc during the execution of a program.

Fortunately, C# has many features to assist you with the initialization process. The instance variable declaration allows you to specify an initialization value for the instance variables (by using the familiar assignment operator to form initialization declarations), and any instance variables that have not been assigned a value explicitly are assigned a default value automatically by the runtime. So in C#, it is impossible to have any non-initialized instance variables roaming your programs. Doesn't this then cover all our initialization needs? No. The following looks closer at automatic initialization and initializing declarations and explains why we also need instance constructors to perform initializations.

Automatic Initializations

The runtime automatically initializes any instance variables that are not initialized explicitly in a declaration statement or by an instance constructor in the source code. The default values provided depend on the instance variable type:

  • Numerical values of type short, int, float, decimal and so on, are assigned the value zero.

  • Values of type char are assigned the Unicode character '\u0000'.

  • Values of type bool are initialized to the value false.

  • Values of reference types are initialized to null.

As a rule of thumb, it is not recommended that you let the runtime perform the initializations of instance variables automatically for the following two reasons:

  • A programmer reading through the code cannot see the initialization values. He or she might not be aware of the previously shown default values.

  • The default values might vary among different compilers and in different versions of a compiler shipped by the same software company.

Moreover, the default value is often different from the value we want to assign to the instance variable. Performing the initialization as part of the instance variable declaration can often fulfill our need to perform manual initializations.

Assign a Value to the Instance Variable As Part of Its Declaration

The following instance variable declaration sets the initial value of balance to 100:

 class Account {     private decimal balance = 100;       ... } 

Recall that the C# syntax rules allow us to position an expression after the assignment operator. With our syntax notation, this could be expressed as follows:

 private decimal balance = <Expression>; 

which initializes balance to the value of <Expression>. This is sufficient for many purposes, but what if we need to employ an if statement, say, to find the correct initialization value? For example, a bank might run a special promotion with the headline "If you open an account before the 1st of June 2001, the opening balance is $200 (paid by us); if you open it between 1st of June and 1st of July it's $100, and after 1st of July $0, so don't miss this special offer!" (A utopian example, but let's say the bank has its headquarters on planet Blipos). Without going into the details about how dates are represented and compared (I have used a bit of pseudocode for those if conditions as shown in the next code snippet), an attempt from a novice programmer to perform this initialization might look like the following lines.


graphics/13infig01.gif

These lines look promising but are invalid. C# only allows if statements and the like inside the bodies of function members. So let's try another avenue following this lead. Let's insert the initialization code in a method called Initialize:

 class Account {     private decimal balance;     public void Initialize()     {         if(OpeningDate is earlier than 1st of June)             balance = 200;         else if (OpeningDate is in between 1st of June and 1st of July)             balance = 100;         else             balance = 0;     } } 

This is valid, apart from the pseudocode representing the conditions of the if statements, of course. One downside, though, is that we must remember to call the Initialize method immediately after the creation of the Account object as in the following lines:

 Account myAccount = new Account(); myAccount.Initialize(); 

It would be easier and less error prone if the Initialize statements were executed automatically simply by writing

 Account myAccount = new Account(); 

This is made possible by the ability of the instance constructor to contain statements that are executed automatically when a new object is created.

Note

graphics/common.gif

Strictly speaking, the Initialize method just shown is not initializing the balance variable. By the time Initialize is called, balance has already been initialized to the default value zero by the runtime.


Note

graphics/common.gif

I chose three options ($200, $100, and $0) instead of just two in the bank promotion example, because the latter would have caused the attentive reader to protest and say: "Wait a minute, this could have been implemented by combining the declaration statement with the conditional operator, because the conditional operator can be positioned anywhere an expression is expected." For example, if the bank's headline was "Open an account before the 1st of June and $100 will be the opening balance; otherwise, $0" it could have been solved by writing the following lines:


 class Account {       private decimal balance = (OpeningDate earlier than 1st July) ? 100 : 0;       ... } 
Other Important Actions Performed at the Time of Object Creation

When an object is created, it might need to perform other actions apart from those related to instance variable initializations. For example, this could be to notify other parts of the program about its creation. A bank application might contain a Statistician class (see Figure 13.1), say, that keeps track of the number of accounts created (accountsCreated) and their total opening balances (totalOpeningBalance), allowing it to calculate things such as the average opening balance. If the Statistician class was equipped with a method called AccountAdded to update those statistics, it could be called from an Account objects during its initialization process as illustrated in Figure 13.1.

Figure 13.1. The Account object calls a method of the BankStatistician class as part of its initial actions.
graphics/13fig01.gif

But like the if statement shown before, C# only allows method calls within the body of a function member. We could include the method call in an Initialization method as previously suggested, but an instance constructor would be a better solution again. The following section shows how to apply instance constructors appropriately and demonstrates how to implement the scenario put forth in Figure 13.1.

Working with Instance Constructors

An instance constructor of a class is a specialized method that is invoked when the new keyword is used to create an object of that class. Instance constructors are used to perform instance variable initializations and other actions needed as part of the creation of an object.

Defining an Instance Constructor

Syntax Box 13.1 shows the definition of an instance constructor. To include an instance constructor in a class, you must write the instance constructor definition within the block of that class alongside its other members.

An optional constructor modifier, such as private or public, can be positioned in front of the identifier of the instance constructor. This allows you to control the accessibility of the instance constructor in the same fashion as that of methods and instance variables. The implications are markedly different, though, which is demonstrated later in this section. For the moment, we will just use the public modifier.

Syntax Box 13.1 TheInstance_Constructor

 Instance_Constructor_Definition::= [<Constructor_modifier>] <Constructor_Identifier> ( [ <Formal_parameter_list> ] graphics/ccc.gif ) [ <Constructor_initializer> ] <Constructor_body> 

where:

 <Constructor_modifier> ::=  private                        ::=  public                        ::=  protected                        ::=  internal <Constructor_initializer> ::=  : base ( [<Argument_list>] )                           ::=  : this ( [<Argument_list>] ) <Constructor_body>::=                 {                     <Statements>                 }                   ::=                 ; 

Notes:

  • The <Constructor_identifier> must be identical to the class identifier in which the constructor resides. For example the constructor in a class called Dog must itself be called dog as illustrated in the example shown in a moment.

  • The instance constructor cannot return a value so no return type is specified, not even the void keyword is used.

  • The <Constructor_initializer> causes another instance constructor to be executed before the statements of this instance constructor.

Example:


graphics/13infig02.gif

Just like a conventional method header, the instance constructor definition must have parentheses positioned after the constructor identifier to contain an optional list of formal parameters. Argument values are passed to these parameters when a new object is created with the new keyword, as demonstrated in a moment.

Similar to its method siblings, an instance constructor can be overloaded by declaring several constructors in the same class, each with a different formal parameter list. The example in Syntax Box 13.1 contains two overloaded instance constructors one has a formal parameter list with one formal parameter of type string, and the other has an empty parameter list.

If the class contains two or more instance constructors (which then consequently must be overloaded), you can optionally specify an instance constructor of the same class to be executed before the statements of the instance constructor you are declaring. This can be done by positioning the constructor initializer : this ( [<Argument_list>] ) after the formal parameter list. The runtime will then execute the instance constructor of the same class that has a formal parameter list matching that of the constructor-initializer's argument list. In the example in Syntax Box 13.1, the instance constructor with an empty argument list (Dog()) is consequently always executed before Dog(string initialName).

The constructor initializer : base ( [<Argument_list>] ) is related to the object-oriented concept inheritance and, for that reason, will not be discussed until this important concept has been presented.

The instance constructor contains a constructor body identical to that of a conventional method body, meaning that it can either consist of an empty statement (a single semicolon) or a group of statements enclosed within a pair of braces. The statements are executed in the same fashion as those of a method when the instance constructor is invoked. However, they should only contain statements directly related to initialization and object creation.

Calling an Instance Constructor

C#'s syntax rules force us to append parentheses after the classname when we create a new object with the new keyword as in the following:

 Cat myCat = new Cat(); 

The parentheses seem to clutter the code unnecessarily, so why must we include them? We already know that new Cat() creates a new object of the Cat class and returns a reference to this object, which, in this case, is assigned to myCat. However, new Cat() has another purpose only briefly mentioned before; it invokes an instance constructor in the same way that a conventional method call invokes a method. Remember that any method call must have parentheses appended after the method name as in the following:

 MyMethod() 

By adhering to this syntax and its associated semantics, the next line:

 Cat() 

must mean "call the method named Cat." As a result, the special method Cat we call an instance constructor is invoked.

The parentheses after the classname (in this case, Cat) can enclose a list of arguments (see Syntax Box 13.2) just like a conventional method call. This argument list must match a corresponding formal parameter list in an instance constructor. If there are two or more instance constructors (which are then overloaded) the instance constructor with the formal parameter list that matches the argument list will be invoked.

So, if the Cat class contains just one instance constructor with an empty parameter list, such as in the following source code snippet:

 class Cat {       private int age;       public Cat()       {             age = 0;       } } 

the parentheses following the class must always be empty, as in

 Cat myCat = new Cat(); 

The general syntax for instantiating an object is shown in Syntax Box 13.2.

Syntax Box 13.2 Instantiating a New Object

 Object_instantiation::=             new <Class_identifier> ( [<Argument_list>] ) 

Notes:

  • There is only one way to create a new object by using the new keyword as shown here.

  • The shown expression returns a reference to the new object, which must be assigned to a reference variable of type reference-to-object-of-class <Class_identifier>. The expression is typically used as part of an assignment statement or as an argument in a method call.

An instance constructor with an empty formal parameter list is called a default instance constructor. Another term for a default instance constructor is a no-argument instance constructor.

In many of our previous examples, a class didn't even contain one instance constructor, such as the Tree class:

 class Tree {       private int age = 0;       ... } 

but we were still able to create a class as if it contained a an explicitly defined default instance constructor as in the following line:

 Tree myTree = new Tree(); 

This is possible because, in the absence of any programmer-specified constructors in the source code, C# automatically creates a default instance constructor (meaning with zero formal parameters) that essentially does nothing apart from allowing you to create a new object. Thus, the following Tree class is semantically identical to the Tree class shown before:

 class Tree {     private int age = 0;     public Tree()         ;      //this is an empty statement doing nothing     ... } 

Caution

graphics/tnt.gif

If you write one or more non-default instance constructors, you must write your own default instance constructor if you need one.

The C# compiler will not include a default instance constructor in a class if you include one or more instance constructors in this class. For example, if you include an instance constructor with one formal parameter, as in the following Car class:

 class Car {       private int odometer;       public Car (int initialOdometer)       {             odometer = initialOdometer;       } } 

the following call to the default instance constructor becomes invalid because the default instance constructor does not exist:

 Car yourCar = new Car(); //Invalid. Default instance constructor is nonexistent 

In this case, you can only call the instance constructor by including the value, which you want to assign to the odometer initially, as an argument. For example, to assign the value 100 as an initial value to the odometer instance variable, you can write the following statement:

 Car yourCar = new Car (100); 


Tip

graphics/bulb.gif

If you include one or more instance constructors in your class, it is often a good idea to also include a default instance constructor.

Even if you don't think your class needs a default constructor at the time you write it, a class is frequently reused for slightly different purposes and circumstances, and often with a sudden need for calling its default constructor. It's much easier to include a default instance constructor while you are constructing your class. The class is then fresh in your mind and you can design the rest of the class to fit in with the default instance constructor.


Overloading Instance Constructors

The arguments you pass to the formal parameters of an instance constructor from the object instantiation expression (shown here as <Argument_list>)

 new <Class_identifier> ( [<Argument_list>] ) 

are often used to initialize the instance variables of the new object. For example, the following Car class includes an instance constructor that allows you to pass the initial values to its brandName and odometer instance variables.

 class Car {       private string brandName;       private int odometer;       public Car (string initialBrandName, int initialOdometer)       {             brandName = initialBrandName;             odometer = initialOdometer;       }       ... } 

This allows us to assign the initial values "BMW" and 1000 to the brandName and odometer instance variables with the following statement:

 Car yourCar = new Car ("BMW", 1000); 

However, depending on where in our program we create a new Car object, we might only have one of the required arguments or perhaps none of them. To accommodate for this need, we can write several different instance constructors with different formal parameter lists accepting different sets of arguments. For example, the following instance constructor accepts just one argument to initialize the brandName:

 public Car (string initialBrandName) {       brandName = initialBrandName;       odometer = 0; } 

Note

graphics/common.gif

To distinguish between the different formal parameter lists included in each overloaded instance constructor, the compiler follows the same procedure as that of conventional overloaded methods. It looks for the number of formal parameters included and their types.

The way an argument list of an instance constructor call is matched with a formal parameter list of an overloaded instance constructor also follows the same rules as that of conventional overloaded methods.

For more details, please refer to the "Method overloading" section in Chapter 12, "Class Anatomy Part I: static Class Members and Method Adventures."


The program presented in Listing 13.1 contains an Account class with three overloaded instance constructors (lines 10 36). They provide flexibility in terms of which arguments we can pass along when a new Account object is created (see lines 106 107, 112, and 115).

Apart from initializing the instance variables of a new Account object, each instance constructor also calls the AccountAdded method of the BankStatistician class (see lines 16, 25, and 34). We thereby implement the idea discussed earlier and illustrated in Figure 13.1 of letting the instance constructor take care of actions, not related to instance variable initializations, that must also be performed when a new Account object is created. Each instance constructor has further been equipped with a unique print statement (lines 17, 26, and 35) so you can monitor when each instance constructor is called.

Listing 13.1 OpeningNewBankAccounts.cs
 01: using System;  02: using System.Collections;  03:  04: class Account  05: {  06:     private decimal balance;  07:     private decimal interestRate;  08:     private string holderName;  09:  10:     public Account(decimal initialBalance, decimal initialInterestRate,  11:         string initialHolderName)  12:     {  13:         balance = initialBalance;  14:         interestRate = initialInterestRate;  15:         holderName = initialHolderName;  16:         BankStatistician.AccountAdded(balance);  17:         Console.WriteLine("New account opened! All info was supplied\ n");  18:     }  19:   20:     public Account(string initialHolderName)  21:     {  22:         balance = 0;  23:         interestRate = 0;  24:         holderName = initialHolderName;  25:         BankStatistician.AccountAdded(balance);  26:         Console.WriteLine("New account opened! Only the holder name was supplied\  graphics/ccc.gifn");  27:     }  28:  29:     public Account()  30:     {  31:         balance = 0;  32:         interestRate = 0;  33:         holderName = "Not assigned yet";  34:         BankStatistician.AccountAdded(balance);  35:         Console.WriteLine("New account opened! No info was supplied\ n");  36:     }  37:  38:     public void SetBalance (decimal newBalance)  39:     {  40:         balance = newBalance;  41:     }  42:  43:     public void SetInterestRate (decimal newInterestRate)  44:     {  45:         interestRate = newInterestRate;  46:     }  47:   48:     public void PrintDetails()  49:     {  50:         Console.WriteLine("Account holder: " + holderName);  51:         Console.WriteLine("Balance: { 0:C} ", balance);  52:         Console.WriteLine("Interest rate: { 0:C}\ n", interestRate);  53:     }  54: }  55:  56: class BankStatistician  57: {  58:     private static int accountsCreated;  59:     private static decimal totalOpeningBalance;  60:  61:     public static void AccountAdded(decimal openingBalance)  62:     {  63:         accountsCreated++;  64:         totalOpeningBalance += openingBalance;  65:     }  66:  67:     public static void PrintAccountStatistics ()  68:     {  69:         Console.WriteLine("Number of accounts created: { 0} ", accountsCreated);  70:         Console.WriteLine("Average opening balance: { 0:N2} ",  71:             ((accountsCreated == 0) ? 0 : (totalOpeningBalance / accountsCreated)));  72:     }  73: }  74:   75: class Bank  76: {  77:     private ArrayList accounts;  78:  79:     public Bank()  80:     {  81:         Console.WriteLine("Congratulations! You have created a new bank");  82:         accounts = new ArrayList();  83:     }  84:  85:     public void OpenNewAccount ()  86:     {  87:         int choice;  88:         decimal initialBalance;  89:         decimal initialInterestRate;  90:         string initialHolderName;  91:   92:         Console.WriteLine("Enter the number matching the information you have now");  93:         Console.WriteLine("1) Balance, interest rate and name of account holder");  94:         Console.WriteLine("2) Only the name of the account holder");  95:         Console.WriteLine("3) No information available");  96:         choice = Convert.ToInt32(Console.ReadLine());  97:         switch (choice)  98:         {  99:             case 1: 100:                Console.Write("Enter opening balance: "); 101:                initialBalance = Convert.ToDecimal(Console.ReadLine()); 102:                Console.Write("Enter initial interest rate: "); 103:                initialInterestRate = Convert.ToDecimal( Console.ReadLine()); 104:                Console.Write("Enter name of account holder: "); 105:                initialHolderName = Console.ReadLine(); 106:                accounts.Add(new Account(initialBalance, initialInterestRate, 107:                    initialHolderName)); 108:                break; 109:             case 2: 110:                 Console.Write("Enter the name of the account holder: "); 111:                 initialHolderName = Console.ReadLine(); 112:                 accounts.Add(new Account(initialHolderName)); 113:                 break; 114:             case 3: 115:                 accounts.Add(new Account()); 116:                 break; 117:             default: 118:                 Console.WriteLine("Invalid input"); 119:                 break; 120:         } 121:     } 122: 123:     public void PrintCurrentAccounts () 124:     { 125:         foreach (Account tempAccount in accounts) 126:         { 127:             tempAccount.PrintDetails(); 128:         } 129:     } 130: } 131: 132: class BankTester 133: { 134:     public static void Main() 135:     { 136:         string answer; 137: 138:         Bank myBank = new Bank(); 139:         Console.WriteLine("Would you like to open an account? Y)es N)o"); 140:         answer = Console.ReadLine().ToUpper(); 141:         while (!(answer == "N")) 142:         { 143:             myBank.OpenNewAccount(); 144:             Console.WriteLine("Would you like to open another account? Y)es N)o"); 145:             answer = Console.ReadLine().ToUpper(); 146:         } 147:         myBank.PrintCurrentAccounts(); 148:         BankStatistician.PrintAccountStatistics(); 149:     } 150: } Congratulations! You have created a new bank Would you like to open an account? Y)es N)o Y<enter> Enter the number matching the information you have now 1) Balance, interest rate and name of account holder 2) Only the name of the account holder 3) No information available 1<enter> Enter opening balance: 1000<enter> Enter initial interest rate: 0.05<enter> Enter name of account holder: Donald Duck<enter> New account opened! All info was supplied Would you like to open another account? Y)es N)o Y<enter> Enter the number matching the information you have now 1) Balance, interest rate and name of account holder 2) Only the name of the account holder 3) No information available 2<enter> Enter the name of the account holder: Mickey Mouse<enter> New account opened! Only the holder name was supplied Would you like to open another account? Y)es N)o Y<enter> Enter the number matching the information you have now 1) Balance, interest rate and name of account holder 2) Only the name of the account holder 3) No information available 3<enter> New account opened! No info was supplied Would you like to open another account? Y)es N)o N<enter> Account holder: Donald Duck Balance: $1,000.00 Interest rate: $0.05 Account holder: Mickey Mouse Balance: $0.00 Interest rate: $0.00 Account holder: Not assigned yet Balance: $0.00 Interest rate: $0.00 Number of accounts created: 3 Average opening balance: 333.33 

The program is only aimed at demonstrating instance constructor overloading. Consequently, the Account and Bank classes (lines 75 130) are stripped-down versions of the more elaborate classes shown in earlier bank simulation examples of this book. However, the Bank class still provides a simple user interface in its OpenNewAccount method to open new accounts. By repeatedly calling this method in line 143 until the user is finished opening new accounts, the Main() method of the BankTester program has an easy job of controlling the overall flow of the program.

Every new Account object is stored in the ArrayList object accounts in the Bank object. This allows us to print out the details of the three accounts we opened during the sample run.

The updates from the instance constructors to the BankStatistician makes it possible for the BankStatistician to print out the number of accounts created and the average opening balance in the last two lines of the output.

The Add method of the ArrayList object accounts accepts arguments of type reference-to-Account-object. Because new Account(...) returns a reference to a new Account object, we can insert this expression directly as an argument in the method call (see lines 106 107, 112, and 115).

Only one BankStatistician is ever needed to keep track of the new accounts opened. Thus, by declaring all the methods and variables of the BankStatistician class static, we can call its AccountAdded method directly through the classname without ever having to go through the trouble of creating a BankStatistician object.

As mentioned earlier, dividing by zero is an invalid calculation and will cause the runtime to generate an exception. If no Account objects are ever opened, accountsCreated will be zero, causing the average calculating expression totalOpeningBalance / accountsCreated (see line 71) to trigger an exception. The conditional operator has been applied in line 71 to effectively prevent the execution to ever reach this far if accountsCreated is equal to zero. In that case, a simple zero is returned.

The Constructor Initializer

We have already briefly described the basic mechanism of the constructor initializer:

 : this ( [<Argument_list>] ) 

Why would you want to use this C# feature? The short answer is that it prevents you from being forced to include the same set of statements in several overloaded instance constructors.

Suppose a class requires a core set of initializations to be performed disregarding which overloaded instance constructor is invoked. We could first try to include the same set of statements performing those initializations in each instance constructor and then add the statements particular to this instance constructor on top. This would work, but it would burden every single instance constructor with a copy of these core statements and burden you with the tedious job of copying and pasting them. As an alternative, it might be possible to gather only the core statements in the default instance constructor and then let each overloaded instance constructor call it through the constructor initializer. It would then be possible to remove all the redundant code found in the initial design.

It doesn't always have to be the default instance constructor that is called by the constructor initializer, as the following example illustrates. Take another look at Listing 13.1. Are any of the statements found in all three instance constructors? Yes, the call to the BankStatistician method AccountAdded:

 BankStatistician.AccountAdded(balance); 

clearly belongs to this category. Furthermore, even though the assignment statements in each constructor that assign values to the balance, interestRate, and holderName instance variables differ in terms of the values assigned, they can all be regarded as special versions of the following three assignment statements (contained in the instance constructor of lines 10 18)

 balance = initialBalance; interestRate = initialInterestRate; holderName = initialHolderName; 

By passing suitable arguments to the instance constructor in lines 10 18, we can set initialBalance, initialInterestRate, and initialHolderName to represent the same values as that of the instance constructors of lines 20 27, or that of the default instance constructor in lines 29 36. This means we can let the instance constructor of line 10 18 take care of the assignments and the call to the BankStatistician method as long as we call this instance constructor with the constructor initializer from the other instance constructors and pass along suitable arguments. The result is shown in Listing 13.2, where two of the instance constructors (lines 17 20 and 22 25) have had all their redundant code removed. This functionality has now been taken over by the instance constructor in lines 7 15 through the constructor initializers in lines 17 and 22, respectively.

Please notice that the code in Listing 13.2 does not compile.

Listing 13.2 Part of Account Class with Redundant Code of Instance Constructors Removed
01: class Account 02: { 03:     private decimal balance; 04:     private decimal interestRate; 05:     private string holderName; 06: 07:     public Account(decimal initialBalance, decimal initialInterestRate, 08:           string initialHolderName) 09:     { 10:         balance = initialBalance; 11:         interestRate = initialInterestRate; 12:         holderName = initialHolderName; 13:         BankStatistician.AccountAdded(balance); 14:         Console.WriteLine("New account opened"); 15:     } 16: 17:     public Account(string initialHolderName) : this (0,0,initialHolderName) 18:     { 19:         Console.WriteLine("Note: Only initial holder name supplied"); 20:     } 21: 22:     public Account() : this (0, 0, "Not assigned yet") 23:     { 24:         Console.WriteLine("Note: No initial information supplied"); 25:     } 

What You Can and Cannot Include in the Argument Expressions of the Constructor Initializer

graphics/common.gif

In line 17 of Listing 13.2, we use the formal parameter initialHolderName as an argument in the constructor initializer. This is acceptable because the constructor initializer is within the scope of the formal parameter of its instance constructor.

On the other hand, it is not possible for the argument expressions of the constructor initializer to access any parts of the object created by the instance constructor to which it is attached. Consequently, any reference to instance variables is invalid and the this keyword cannot be applied in this part of the program, as illustrated in the following line:


graphics/13infig03.gif


Note

graphics/common.gif

The argument list of the constructor initializer cannot match the formal parameter list of the instance constructor to which it is applied.

Take a look at the constructor initializer of this example. It contains an argument list that matches the formal parameter list of the instance constructor to which it is applied:


graphics/13infig04.gif

The instance constructor is, in effect, asking itself to be executed before its own method body in a recursive fashion. This next instance constructor execution would again ask itself to be executed before itself and so on, creating an infinite chain of calls to itself.

Recursion is a powerful technique when applied to conventional methods, but it doesn't make much sense if attempted on instance constructors. Consequently, the argument list of the constructor initializer cannot match the formal parameter list of the instance constructor to which it is applied.


Calling Other Conventional Methods of the same Class from Within an Instance Constructor

graphics/common.gif

You have already seen how a static method like AccountAdded of the BankStatistician class can be called from within an instance constructor of the Account class in Listing 13.1. You can also call a method from within the same class as if you were calling it from a conventional method. However, it is important to keep the actions (whether they are statements executed inside the instance constructor or residing inside any other method called from an instance constructor) strictly related to the creation of the object.


private Instance Constructors

You can substitute the first keyword public (used in the previous instance constructor examples) of the instance constructor declaration with the keyword private to make your instance constructor private. A private instance constructor cannot be called from anywhere outside the class. This resembles the characteristics of private methods and instance variables. In effect, a private instance constructor cannot be used to create an object, so a class containing only private instance constructors effectively prevents any objects of this class to be instantiated from outside the class. This mechanism is often used to prevent classes that only contain static members, which were never meant to be instantiated, from accidentally being instantiated in other parts of the program.

Our BankStatistician class in Listing 13.1 is an example of a static members-only class. It would be illogical and not fit in with the overall design of the program to instantiate this class. To effectively prevent this from happening, we simply need to insert a private default instance constructor in the BankStatistician class, as the following lines demonstrate.


graphics/13infig05.gif


   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net