Constructors and Destructors


When you define a class in C#, there is often no need to define associated constructors and destructors, because the base class System.Object provides a default implementation for you. However, you can provide your own if required, enabling you to initialize and clean up after your objects, respectively.

A simple constructor can be added to a class using the following syntax:

class MyClass { public MyClass() { // Constructor code. } }

This constructor has the same name as the class that contains it, has no parameters (making it the default constructor for the class), and is public so that objects of the class may be instantiated using this constructor (check back to the discussion in the last chapter for more information on this).

You can also use a private default constructor, meaning that object instances of this class cannot be created using this constructor (see the discussion in the last chapter):

class MyClass { private MyClass() { // Constructor code. } }

Finally, you can add nondefault constructors to your class in a similar way, simply by providing parameters. For example:

class MyClass { public MyClass() { // Default constructor code. }     public MyClass(int myint) { // Nondefault constructor code (uses myInt). } }

There is no limit to the number of constructors you can supply (apart from running out of memory or distinct sets of parameters of course, so maybe "almost limitless" is more appropriate).

Destructors are declared using a slightly different syntax. The destructors used in .NET (and supplied by the System.Object class) is called Finalize(), but this isn't the name you use to declare a destructor. Instead of overriding Finalize() you use the following:

class MyClass { ~MyClass() { // Destructor body. } } 

Thus the destructor of a class is declared by the class name (like the constructor is), with the ~ prefix. The code in the destructor will be executed when garbage collection occurs, allowing you to free resources. After this destructor is called, implicit calls to the destructors of base classes also occur, including a call to Finalize() in the System.Object root class. This technique allows the .NET Framework to ensure that this occurs, because overriding Finalize() would mean that base class calls would need to be explicitly performed, which is potentially dangerous (you see how to call base class methods in the next chapter).

Constructor Execution Sequence

If you perform multiple tasks in the constructors of a class, it can be handy to have this code in one place, which has the same benefits as splitting code into functions, as you saw in Chapter 6. You could do this using a method (see Chapter 10), but C# provides a nice alternative. Any constructor can be configured to call any other constructor before it executes its own code.

Before looking at this, though, you need to take a closer look at what happens by default when you instantiate a class instance. Apart from facilitating the centralization of initialization code as noted above, this is also worth knowing about in its own right. It is often the case during development that objects don't behave quite as you expect them to owing to errors during constructor calling — usually due to a base class somewhere in the inheritance hierarchy of your class that you are not instantiating correctly, or where information is not being properly supplied to base class constructors. Understanding what happens when during this phase of the lifecycle of an object can make it much easier to solve this sort of problem.

In order for a derived class to be instantiated its base class must be instantiated. In order for this base class to be instantiated the base class of this base class must be instantiated, and so on all the way back to System.Object. The result of this is that whatever constructor you use to instantiate a class, System.Object.Object() is always called first.

If you use a nondefault constructor of a class, then the default behavior is to use a constructor on the base class that matches the signature of this constructor. If none is found, then the default constructor for the base class is used (which will always happen for the ultimate root System.Object, because this class has no nondefault constructors). Here's a quick example of this to illustrate the sequence of events. Consider the following object hierarchy:

 public class MyBaseClass { public MyBaseClass() { } public MyBaseClass(int i) { } } public class MyDerivedClass : MyBaseClass { public MyDerivedClass() { } public MyDerivedClass(int i) { } public MyDerivedClass(int i, int j) { } } 

You could instantiate MyDerivedClass in the following way:

 MyDerivedClass myObj = new MyDerivedClass(); 

In this case, the following sequence of events will occur:

  • The System.Object.Object() constructor will execute.

  • The MyBaseClass.MyBaseClass() constructor will execute.

  • The MyDerivedClass.MyDerivedClass() constructor will execute.

Alternatively, you could use the following:

 MyDerivedClass myObj = new MyDerivedClass(4); 

Here, the sequence will be:

  • The System.Object.Object() constructor will execute.

  • The MyBaseClass.MyBaseClass(int i) constructor will execute.

  • The MyDerivedClass.MyDerivedClass(int i) constructor will execute.

Finally, you could use the following:

 MyDerivedClass myObj = new MyDerivedClass(4, 8); 

This results in the following sequence:

  • The System.Object.Object() constructor will execute.

  • The MyBaseClass.MyBaseClass() constructor will execute.

  • The MyDerivedClass.MyDerivedClass(int i, int j) constructor will execute.

This system works fine and ensures that any inherited members are accessible to constructors in your derived classes. However, there are times when a little more control over the events that take place is required, or just desirable. For example, in the last instantiation example, you might want to have the following sequence:

  • The System.Object.Object() constructor will execute.

  • The MyBaseClass.MyBaseClass(int i) constructor will execute.

  • The MyDerivedClass.MyDerivedClass(int i, int j) constructor will execute.

Using this you could place the code that uses the int i parameter in MyBaseClass(int i), meaning that the MyDerivedClass(int i, int j) constructor would have less work to do — it would only need to process the int j parameter. (This assumes that the int i parameter has an identical meaning in both cases, which might not always be the case, but in practice with this kind of arrangement it usually is.) C# allows you to specify this kind of behavior should you wish.

To do this, you simply specify the base class constructor to use in the definition of the constructor in your derived class as follows:

public class MyDerivedClass : MyBaseClass {     ...     public MyDerivedClass(int i, int j) : base(i)    {    } }

The base keyword directs the .NET instantiation process to use the base class constructor matching the signature specified. Here, you are using a single int parameter, so MyBaseClass(int i) will be used. Doing this means that MyBaseClass() will not be called, giving you the sequence of events listed prior to this example — exactly what you wanted here.

You can also use this keyword to specify literal values for base class constructors, perhaps using the default constructor of MyDerivedClass to call a nondefault constructor of MyBaseClass:

public class MyDerivedClass : MyBaseClass { public MyDerivedClass() : base(5)    {    }    ... }

This gives you the following sequence:

  • The System.Object.Object() constructor will execute.

  • The MyBaseClass.MyBaseClass(int i) constructor will execute.

  • The MyDerivedClass.MyDerivedClass() constructor will execute.

As well as this base keyword, there is one more keyword that you can use here: this. This keyword instructs the .NET instantiation process to use a nondefault constructor on the the current class before the specified constructor is called. For example:

public class MyDerivedClass : MyBaseClass { public MyDerivedClass() : this(5, 6)    {    }        ...        public MyDerivedClass(int i, int j) : base(i)    {    } }

Here, you will have the following sequence:

  • The System.Object.Object() constructor will execute.

  • The MyBaseClass.MyBaseClass(int i) constructor will execute.

  • The MyDerivedClass.MyDerivedClass(int i, int j) constructor will execute.

  • The MyDerivedClass.MyDerivedClass() constructor will execute.

The only limitation to all this is that you can only specify a single constructor using the this or base keywords. However, as demonstrated in the last example, this isn't much of a limitation, because you can still construct fairly sophisticated execution sequences.

You see this technique in action a little later in the book.




Beginning Visual C# 2005
Beginning Visual C#supAND#174;/sup 2005
ISBN: B000N7ETVG
EAN: N/A
Year: 2005
Pages: 278

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