Construction and Disposal

 
Chapter 3 - Object-Oriented C#
bySimon Robinsonet al.
Wrox Press 2002
  

In this section we're going to look at how to define constructors and destructors for your classes in C#. Recall from Chapter 2 that constructors exist to provide automatic initialization routines for objects and classes, while destructors are in principle available to supply automatic cleanup code when an object is destroyed .

Constructors

The syntax for declaring basic constructors in C# is the same as in Java and C++. We declare a method that has the same name as the containing class, and which does not have any return type:

   public class MyClass     {     public MyClass()     {     }     // rest of class definition   

As in C++ and Java, it's not necessary to provide a constructor for your class. We haven't supplied one for any of our examples so far in the book. In general, if you don't explicitly supply any constructor, the compiler will just make a default one up for you behind the scenes. It'll be a very basic constructor that just initializes all the member fields to their normal default values (empty string for strings, zero for numeric data types, and false for bool s, as we've already mentioned). Often, that will be adequate; otherwise , you'll need to write your own constructor.

For C++ programmers: because primitive fields in C# are by default initialized by being zeroed out, whereas primitive fields in C++ are by default uninitialized , you may find you don't need to write constructors in C# as often as you would in C++.

Constructors follow the same rules for overloading as other methods . In other words, you can provide as many overloads to the constructor as you wish, providing they are clearly different in signature:

   public MyClass()   // zero-parameter constructor     {     // construction code     }     public MyClass(int number)   // another overload     {     // construction code     }   

Note however that if you supply any constructors that take parameters, then the compiler will not automatically supply a default one. This is only done if you have not explicitly defined any constructors at all. In the following example, because we have explicitly defined a one-parameter constructor, the compiler will assume that this is the only constructor we wish to be available, and so will not implicitly supply any others:

   public class MyNumber     {     private int number;     public MyNumber(int number)     {     this.number = number;     }     }   

The above code also illustrates typical use of the this keyword to distinguish member fields from parameters of the same name. If we now try instantiating a MyNumber object using a no-parameter constructor, we will get a compilation error:

   MyNumber numb = new MyNumber();   // causes compilation error   

We should mention that it is possible to define constructors as private or protected, so that they are invisible to code in unrelated classes too:

 public class MyNumber {    private int number;   private MyNumber(int number)   // another overload   {       this.number = number;     } } 

In this example we haven't actually defined any public or even any protected constructors for MyNumber . This would actually make it impossible for MyNumber to be instantiated by outside code using the new operator (though you might write a public static property or method in MyNumber that can instantiate the class). This is useful in two situations:

  • If your class serves only as a container for some static members or properties, and therefore should never be instantiated

  • If you want the class to only ever be instantiated by calling some static member function

Note, however, that if you declare only one or more private constructors, you will also make it impossible for any class derived from your class to ever be instantiated by any means whatsoever. This is because, as we will see soon when we examine derived class constructors, a derived class will always expect to call a base constructor - which means there must be an appropriate base class constructor to which the derived class has access.

Static Constructors

One novel feature of C# is that it is also possible to write a static no-parameter constructor for a class. Such a constructor will only ever be executed once, as opposed to the constructors we've written so far, which are instance constructors, and executed whenever an object of that class is created. One reason why you might want a static constructor is to initialize the values of any static variables . There is no equivalent to the static constructor in C++.

   class MyClass     {     static MyClass()     {     // initialization code     }     // rest of class definition     }   

One reason for writing a static constructor would be if your class has some static fields or properties that need to be initialized from an external source before the class is first used.

The .NET runtime makes no guarantees about when a static constructor will be executed, so you should not place any code in it that relies on it being executed at a particular time (for example, when an assembly is loaded). Nor is it possible to predict what order static constructors of different classes will execute. However, what is guaranteed is that the static constructor will run at most once, and that it will be invoked before your code makes any reference to the class. In practice, in C#, the static constructor usually seems to be executed immediately before the first call to a member of the class.

Notice that the static constructor does not have any access modifiers. It's never called by any other C# code, but always by the .NET runtime when the class is loaded, so any access modifier like public or private would be meaningless. For this same reason, the static constructor cannot ever take any parameters, and there can only ever be one static constructor for a class. It should also be obvious that a static constructor can only access static members, not instance members, of the class.

Note that it is perfectly possible to have a static constructor and a zero-parameter instance constructor defined in the same class. Although the parameter lists are identical, there is no conflict here because the static constructor is executed when the class is loaded, but the instance constructor is executed whenever an instance is created - so there won't be any confusion about which constructor gets executed when!

One thing to watch is that if you have more than one class that has a static constructor, which static constructor will be executed first is undefined. This means that you should not put any code in a static constructor that depends on other static constructors having been or not having been executed. On the other hand, if any static fields have been given default values, these will be allocated before the static constructor is called.

We'll now present a sample that illustrates the use of a static constructor. The sample is imaginatively called StaticConstructor , and is based on the idea of a program that has user preferences (which are presumably stored in some configuration file). To keep things simple, we'll assume just one user preference - a quantity called BackColor , which might represent the background color to be used in an application. And since we don't want to get into coding up reading data from an external source here, we'll instead make the assumption that the preference is to have a background color of red on weekdays and green at weekends. All the program will do is display the preference in a console window - but this is enough to see a static constructor at work.

The preferences will be controlled by a class, UserPreferences . Since it doesn't make sense to have more than one set of preferences in a running application, everything in this class will be static. We'll even make the class sealed and define a private constructor to ensure it can't ever get instantiated. Here's the definition of UserPreferences .

   namespace Wrox.ProCSharp.OOCSharp     {     sealed class UserPreferences     {     public static readonly Color BackColor;     static UserPreferences()     {     DateTime now = DateTime.Now;     if (now.DayOfWeek == DayOfWeek.Saturday     now.DayOfWeek == DayOfWeek.Sunday)     BackColor = Color.Green;     else     BackColor = Color.Red;     }     private UserPreferences()     {     }     }     }   

This code shows how the color preference is stored in a static variable, which is initialized in the static constructor. We have declared this field as readonly , which means that its value can only be set in a constructor. We'll look at readonly fields in more detail later. The code makes use of the (static) Now and (instance) DayOfWeek properties of the System.DateTime struct. It also uses the System.Drawing.Color struct that we will investigate more fully in Chapter 19. In order to use this struct, we need to reference the System.Drawing.dll assembly when compiling, and add a using statement for the System.Drawing namespace.

 using System;   using System.Drawing;   

We test the static constructor with this code:

   class MainEntryPoint     {     static void Main(string[] args)     {     Console.WriteLine("User-preferences: BackColor is: " +     UserPreferences.BackColor.ToString());     }     }   

Compiling and running this code gives this:

  StaticConstructor  User-preferences: BackColor is: Color [Red] 

Calling Constructors from Other Constructors

You may sometimes find yourself in the situation where you have several constructors in a class, perhaps to accommodate some optional parameters, for which the constructors have some code in common. For example, consider this situation:

   class Car     {     private string description;     private uint nWheels;     public Car(string model, uint nWheels)     {     this.description = description;     this.nWheels = nWheels;     }     public Car(string model)     {     this.description = description;     this.nWheels = 4;     }     // etc.   

Both constructors initialize the same fields. It would clearly be neater to place all the code in one place, and C# has a special syntax, known as a constructor initializer , to allow this.

   class Car     {     private string description;     private uint nWheels;     public Car(string model, uint nWheels)     {     this.description = description;     this.nWheels = nWheels;     }     public Car(string model) : this(model, 4)     {     }     // etc   

In this context, the this keyword simply causes the constructor with the nearest matching parameters to be called. Note that the constructor initializer is executed before the body of the constructor. Now say the following code is run:

   Car myCar = new Car("Proton Persona");   

In this case, the two-parameter constructor will execute before any code in the body of the one-parameter constructor (though in this particular case, since there is no code in the body of the one-parameter constructor, it makes no difference).

A C# constructor initializer may contain either one call to another constructor in the same class (using the syntax just presented) or one call to a constructor in the immediate base class (using the same syntax, but using the keyword base instead of this ). It is not possible to put more than one call in the initializer.

The syntax for constructor initializers in C# is similar to that for constructor initialization lists in C++, but C++ developers should beware. Behind the similarity in syntax, C# initializers follow very different rules for what can be placed in them. Whereas you can use a C++ initialization list to indicate initial values of any member variables or to call a base constructor, the only thing you can put in a C# initializer is one call to one other constructor. This forces C# classes to follow a strict sequence for how they get constructed , where C++ allows some laxity. As we'll see soon, the sequence enforced by C# arguably amounts to no more than good programming practice anyway, and so is beneficial.

Constructors of Derived Classes

We've now seen how constructors work for simple classes, and we've also seen how classes can derive from other classes. An interesting question arises as to what happens when you start defining your own constructors for classes that are in a hierarchy, inherited from other classes that may also have custom constructors. This is quite a subtle issue so we'll investigate it in some detail.

When the compiler supplies default constructors everywhere, there is actually quite a lot going on, but the compiler is able to arrange it so that things work out nicely all throughout the class hierarchy and every field in every class gets initialized to whatever its default value is. From the moment that we add a constructor of our own, however, we are effectively taking control of construction right through the hierarchy, and we will therefore need to make sure that we don't inadvertently do anything to prevent construction through the hierarchy from taking place smoothly.

You might be wondering why there is any special problem with derived classes. The reason is that when you create an instance of a derived class, there is actually more than one constructor at work. The constructor of the class you instantiate isn't by itself sufficient to initialize the class - the constructors of the base classes must also be called. That's why I've been talking about construction through the hierarchy.

To see why base class constructors must be called, we're going to develop an example based on a cell phone company called MortimerPhones . If you've read Nevermore60Customer , which represents any customer on a particular tariff, called the Nevermore60 tariff. All customers have a name, represented by a private field. Under the Nevermore60 tariff , the first few minutes of the customer's call time are charged at a higher cost, necessitating the need for a field, highCostMinutesUsed , which details how far into these higher cost minutes each customer has reached. This means that the class definitions look like this:

   abstract class GenericCustomer     {     private string name;     // lots of other methods etc.     }     class Nevermore60Customer : GenericCustomer     {     private uint highCostMinutesUsed;     // other methods etc.     }   

We won't worry about what other methods might be implemented in these classes, as we are concentrating solely on the construction process here. And if you download the sample code for this chapter, you'll find the class definitions include only the constructors.

Let's look at what happens when you use the new operator to instantiate a Nevermore60Customer like this:

   GenericCustomer arabel = new Nevermore60Customer();   

Clearly both of the member fields name and highCostMinutesUsed must be initialized when arabel is instantiated. If we don't supply constructors of our own, but rely simply on the default constructors, then we'd expect name to be initialized to the null reference, and highCostMinutesUsed to zero. Let's look in a bit more detail at how this actually happens.

The highCostMinutesUsed field presents no problem: the default Nevermore60Customer constructor supplied by the compiler will initialize this to zero.

What about name ? Looking at the class definitions, it's clear that the Nevermore60Customer constructor can't initialize these values. This field is declared as private , which means that derived classes don't have access to it. So the default Nevermore60Customer constructor simply won't even know that this field exists. The only things that have that knowledge are other members of GenericCustomer . This means that if name is going to be initialized, that'll have to be done by some constructor in GenericCustomer . No matter how big your class hierarchy is, this same reasoning applies right down to the ultimate base class, System.Object .

Now that we have an understanding of the issues involved, we can look at what actually happens whenever a derived class is instantiated. Assuming default constructors are used throughout, the compiler first grabs the constructor of the class it is trying to instantiate - in this case Nevermore60Customer . The first thing that the default Nevermore60Customer does is attempt to run the default constructor for the immediate base class, GenericCustomer . This is how default constructors always behave in C#. Then the GenericCustomer constructor attempts to run the constructor for its immediate base, System.Object doesn't have any base classes, so its constructor just executes and returns control to the GenericCustomer constructor. That constructor now executes, initializing name to null , before returning control to the Nevermore60Customer constructor. That constructor in turn executes, initializing highCostMinutesUsed to zero, and exits. At this point, the Nevermore60Customer instance has been successfully constructed and initialized.

The net result of all this is that the constructors are called in order of System.Object first, then progressing down the hierarchy until we reach the class being instantiated. Notice also that in this process, each constructor handles initialization of the fields in its own class. That's how it should normally work, and when you start adding your own constructors you should try to stick to that principle where possible.

Incidentally, working up the hierarchy is a sensible order because it means that if required, a constructor for the derived class can, in its implementation, call up base class methods, properties and any other members it is allowed to be aware of, confident that the base class has already been constructed and its fields initialized. It also means that if the derived class doesn't like the way that the base class has been initialized, it can change the initial values of the data, provided it has access to do so. However, good programming practice means you'll try to avoid that situation occurring, if you can, but trust the base class constructor to deal with its fields.

Anyway, now we've understood how the process of construction works, we can start fiddling with it by adding our own constructors.

Adding a No-Parameter Constructor in a Hierarchy

We'll take the simplest case first and see what happens if we simply replace the default constructor somewhere in the hierarchy with another constructor that takes no parameters. Suppose that we decide that we want everyone's name to be initially set to < no name > instead of to the null reference. We'd modify the code in GenericCustomer like this:

 public abstract class GenericCustomer    {       private string name;   public GenericCustomer()     : base()  // we could omit this line without affecting the compiled code     {     name = "<no name>";     }   

Adding this code will work fine. Nevermore60Customer still has its default constructor, so the sequence of events described above will proceed as before, except that the compiler will use our custom GenericCustomer constructor instead of generating a default one, so the name field will be initialized to < no name > as required.

Notice that in our constructor, we've added an explicit call to the base class constructor before the GenericCustomer constructor is executed, using the same syntax as we were using earlier when we covered how to get different overloads of constructors to call each other. The only difference is that this time we use the base keyword instead of this , to indicate it's a constructor to the base class rather than a constructor to this class we want to call. There are no parameters in the brackets after the base keyword, so we are calling the no-parameter System.Object constructor, just as would happen by default.

In fact, we could have left that line out, and just written the following, as we've done for most of the constructors so far in the chapter:

   public GenericCustomer()     {     name = "<no name>";     }   

If the compiler doesn't see any reference to another constructor before the opening curly brace , it assumes that we intended to call the base class constructor; this fits in with the way that we've just explained default constructors work.

Important 

The base and this keywords are the only keywords allowed in the line which calls another constructor. Anything else will cause a compilation error. Also note that only one other constructor can be specified.

So far this code works fine. One good way to mess up the progression through the hierarchy of constructors, however, is to declare a constructor as private :

   private GenericCustomer()   {          name = "<no name>";       } 

If you try this, you'll find you get an interesting compilation error, which could really throw you if you don't understand how construction down a hierarchy works:

 'Wrox.ProCSharp.OOCSharp.GenericCustomer.GenericCustomer()' is inaccessible due to its protection level 

The interesting thing is that the error occurs not in the GenericCustomer class, but in the derived class, Nevermore60Customer . What's happened is that the compiler has tried to generate a default constructor for Nevermore60Customer , but not been able to because the default constructor is supposed to invoke the no-parameter GenericCustomer constructor. By declaring that constructor as private , we've made it inaccessible to the derived class. A similar error will occur if we supply a constructor to GenericCustomer , which takes parameters, but no no-parameter constructor. In this case the compiler will not generate a default constructor for GenericCustomer , so when it tries to generate the default constructors for any derived class, it'll again find that it can't because there is no no-parameter base class constructor available. The way round this problem would be to add your own constructors to the derived classes, even if you don't actually need to do anything in these constructors, so that the compiler doesn't try to generate any default constructor for them.

However, at this point I think we've had enough discussion of what can go wrong with constructors. We've got all the theoretical background we need and we're ready to move on to an example of how you should add constructors to a hierarchy of classes. In the next section we'll start adding constructors that take parameters to the MortimerPhones sample.

Adding Constructors with Parameters to a Hierarchy

We're going to start by arranging that customers can only be instantiated on supplying their name. This will mean writing a one-parameter constructor for GenericCustomer :

 abstract class GenericCustomer    {       private string name;   public GenericCustomer(string name)     {     this.name = name;     }   

So far so good, but as we've just said, this will cause a compilation error when the compiler tries to create a default constructor for any derived classes, since the default compiler-generated constructors for Nevermore60Customer will both try to call a no-parameter GenericCustomer constructor, but GenericCustomer does not now possess such a constructor. Therefore, we'll need to supply our own constructors to the derived classes to avoid a compilation error.

 class Nevermore60Customer : GenericCustomer {    private uint highCostMinutesUsed;   public Nevermore60Customer(string name)     :   base(name)     {     }   

Now instantiation of Nevermore60Customer objects can only take place when a string containing the customer's name is supplied, which is what we want anyway. The interesting thing is what our Nevermore60Customer constructor does with this string. Remember that it can't initialize the name field itself, because it has no access to a private field in a base class. Instead, it passes the name through to the base class for the GenericCustomer constructor to handle. It does this by specifying that the base class constructor to be executed first is the one that takes the name as a parameter. Other than that, it doesn't take any action of its own.

Now we're now going to make things a bit more complex. We're going to investigate what happens if you have different overloads of the constructor as well as a class hierarchy to deal with.

To this end we're going to assume that Nevermore60 customers may have been referred to MortimerPhones by a friend, you know, one of these sign up a friend and get a discount offers. This means that when we construct a Nevermore60Customer , we may need to pass in the referrer's name as well. In real life the constructor would have to do something complicated with the name, like process the discount, but here we'll just store the referrer's name in another field.

The Nevermore60Customer definition will look like this at this stage:

 class Nevermore60Customer : GenericCustomer    {   public Nevermore60Customer(string name, string referrerName)     : base(name)     {     this.referrerName = referrerName;     }         private string referrerName;   private uint highCostMinutesUsed; 

The constructor takes the name and passes it to the GenericCustomer constructor for processing. referrerName is the variable that is our responsibility here, so the constructor deals with that parameter in its main body.

However, not all Nevermore60Customers will have a referrer, so we still need a constructor that doesn't require this parameter (or, equivalently, a constructor that gives us a default value for it). In fact we will specify that if there is no referrer, then the referrerName field should be set to < None >. Here's what the one-parameter constructor that does this looks like:

   public Nevermore60Customer(string name)     : this(name, "<None>")     {     }   

We've now got all our constructors set up correctly. It's rather instructive to examine the chain of events that now occurs when we execute a line like this:

 GenericCustomer arabel = new Nevermore60Customer("Arabel Jones"); 

The compiler sees that it needs a one-parameter constructor that takes one string, so the constructor it'll identify is the last one that we've defined:

 public Nevermore60Customer(string Name)          : this(Name, "<None>") 

When we instantiate arabel , this constructor will be called. It will immediately transfer control to the corresponding Nevermore60Customer 2-parameter constructor, passing it the values Arabel Jones , and < None >. Looking at the code for this constructor, we see that it in turn immediately passes control to the one-parameter GenericCustomer constructor, giving it the string Arabel Jones , and in turn that constructor passes control to the System.Object constructor executes. Next comes the GenericCustomer constructor; this will initialize the name field. Then the Nevermore60Customer 2-parameter constructor gets control back, and sorts out initializing the referrerName to < None >. Finally, the Nevermore60Customer one-parameter constructor gets to execute; this constructor doesn't do anything else.

Although this is quite a complicated process as you can see, it's actually a very neat and well-designed one too. Each constructor has handled initialization of the variables that are obviously its responsibility, and in the process our class has been correctly instantiated and prepared for use. If you follow the same principles when you write your own constructors for your classes, you should find that even the most complex classes get initialized smoothly and without any problems.

Cleaning up: Destructors and Dispose()

We've seen that constructors allow you to specify certain actions that must take place whenever an instance of a class is created, so you might be wondering if it's possible to do the same thing whenever a class instance is destroyed. Indeed, if you're familiar with C++, you'll know that C++ allows you to do just that, by specifying a method known as a destructor . Destructors are called whenever a class instance goes out of scope or is otherwise removed from memory. Experienced C++ developers make extensive use of destructors, and sometimes not only to clean up resources, but also to provide debugging information or perform other tasks .

C# does support destructors, but they are used far less often than in C++, and the way they work is very different. Because reference objects in .NET and C# are removed by the garbage collector, there can be a delay between their the reference going out of scope and the object actually being deleted. This is perhaps the only disadvantage of a garbage-collection system of memory management. If your object is holding scarce and critical resources which need to be freed as soon as possible then you won't want to wait for garbage collection. Because of this, the destruction paradigm in C# works in two stages.

  1. The class should implement the standard interface System.IDisposable . This means implementing the method, IDisposable.Dispose() . This method is explicitly called by client code when an object is no longer required.

  2. A destructor may be defined, which will be automatically invoked when the object is garbage-collected . However, the aim should be that it's the client's responsibility to notify the object (by calling Dispose() ) when it no longer needs it. The destructor is only there as a backup mechanism in case some badly -behaved client doesn't call Dispose() .

    Although we talk about destructors in C#, in the underlying .NET architecture, these are known as Finalize() methods. When you define a destructor in C#, what is emitted into the assembly by the compiler is actually a method called Finalize() . That's something that doesn't affect any of your sourcecode, but you'll need to be aware of the fact if for any reason you wish to examine the contents of the emitted assembly.

In general, if objects of some class can contain references to other managed objects that are large and should be cleaned up as soon as we no longer need our object, then that class should implement Dispose() . If a class holds unmanaged resources, then it should implement both Dispose() and a destructor.

There are really two issues we need to consider here: what the syntax for destructors and Dispose() is, and how to write good implementations of these methods. We'll look at the syntax first. This class contains a Dispose() method and a destructor.

Dispose() is a normal method. So the syntax for defining it is no different from any other method.

   class MyClass : IDisposable     {     public void Dispose()     {     // implementation     }     ~MyClass()   // destructor. Only implement if MyClass directly holds     // unmanaged resources.     {     // implementation     }     // etc.   

Notice that the class is derived from IDisposable . Notice also the signature of Dispose() : it returns a void and takes no parameters.

The syntax for the destructor will be familiar to C++ developers. It looks like a method, with the same name as the containing class, but prefixed with a tilde ( ~ ). It has no return type, takes no parameters and no access modifiers (it is always called by the .NET runtime, not by other C# code, so access modifiers would make no sense here).

Implementing Dispose() and a Destructor

Destructors are called when an object is destroyed. There are several things you should bear in mind when you implement a destructor:

  • Destructors are not deterministic. This means that there's no way in general to predict when the instance will be destroyed, which means that you can't predict when a destructor will be called. In general, instances will be destroyed when the garbage collector detects that they are no longer referenced, but to a large extent the garbage collector comes into action when the .NET runtime decides it's needed - and that's up to the .NET runtime, not your program. Hence, you should not place any code in the destructor that relies on being run at a certain time, and you shouldn't even rely on the destructor being called for different class instances in any particular order.

  • You can force the garbage collector to run at a certain point in your code, if you want, by calling System.GC.Collect() . System.GC is a .NET base class that represents the garbage collector, and the Collect() method actually calls the garbage collector. However, this is intended for rare situations in which you know that for example, it's a good time to call the garbage collector because a very large number of objects in your code have just stopped being referenced. You certainly wouldn't normally go to the trouble of calling up the entire garbage collection process just to get one destructor called!

  • In general, you are advised not to implement a destructor unless your class really does need it. Due to the way the garbage collector has been implemented, if an object implements a destructor , that puts a significant performance hit on garbage-collecting that object. It also delays the final removal of that object from memory. Objects that do not have a destructor get removed from memory in one pass of the garbage collector, but objects that have destructors require two passes to be destroyed: the first one calls the destructor without removing the object, the second actually deletes the object. Do not implement a destructor unless your class directly holds some unmanaged resource (for example a database connection). If your class merely holds references to other objects that hold on to unmanaged resources, then do not implement a destructor - those other classes ought instead to have destructors of their own.

Now on to the typical usage pattern of destructors. We'll show you the general code structure you should implement, then discuss the reasons for this. The code you write ought to look like this.

   public class ResourceHolder : IDisposable     {     public void Dispose()     {     Dispose(true);     GC.SuppressFinalize(this);     }     protected virtual void Dispose(bool disposing)     {     if (disposing)     {     // Cleanup managed objects     }     // Cleanup unmanaged objects     }     ~ResourceHolder()     {     Dispose (false);     }     }   

We see from this that there is a second overload of Dispose() , which takes one bool parameter - and this is the method that really does the cleaning up. Dispose(bool) is called by both the destructor, and by the no-parameter Dispose() method (which we'll refer to as IDisposable.Dispose() ). The point of this approach is to ensure that all cleanup code is in one place.

The parameter passed to Dispose(bool) indicates whether Dispose(bool) has been invoked by the destructor or by IDisposable.Dispose() - Dispose(bool) should not be invoked from anywhere else in your code . The idea is this:

  • If a client calls IDisposable.Dispose() then that client is indicating that all resources associated with that object - both managed and unmanaged - should be cleaned up.

  • If a destructor has been invoked, then all resources still need in principle to be cleaned up. However, in this case, we know that the destructor must have been called by the garbage collector - and we can assume that the garbage collector will independently be ensuring that any other associated managed resources are being removed from the managed heap (and in the process will be invoking their destructors if they have any defined). Hence the only thing we need to explicitly do from the destructor is clean up any unmanaged resources. Not only that, but there is another very good reason for not referring to other managed objects from code that has been invoked from the destructor. Since it's not possible to predict what order objects will be destroyed in, any such managed objects may well have got destroyed first - in which case our references to them will be invalid anyway.

Looking again at the above code, we see that the final thing to happen in a call to IDisposable.Dispose() is a call to a method in the System.GC .NET Framework base class, GC . SuppressFinalize() . GC is the class that is responsible for managing the garbage collector, and the SuppressFinalize() method tells the garbage collector that a class no longer needs to have its destructor called - that makes the garbage collection process more efficient. Since Dispose() has already done all the cleanup required, there's nothing left for the destructor to do. Calling SuppressFinalize() means that the garbage collector will treat that object as if it doesn't have a destructor at all.

By doing it this way, you have a backup. If the client code remembers to call Dispose() , then the resources get cleaned up in good time. If the client forgets, then all is not lost because the destructor will be called eventually when the object is garbage-collected.

Note that there are never any parameters to a destructor, no return type, and no access modifier. Just as with constructors, each destructor should clean up only those resources that are defined within its own class. If any code in any of the base classes holds on to external resources, then those should be cleaned up in destructors in the relevant base classes. And there is no need to explicitly call the base class's destructor - the compiler will automatically arrange for all destructors that have been defined through the class hierarchy to be called.

Close() vs. Dispose()

The difference between the Close() and Dispose() methods is largely one of convention. Close() tends to suggest that it's a resource that might later be reopened, while Dispose() has more of an implication of finality - calling Dispose() means that the client has finished with this particular object for good. You can implement either one or both of these methods, but to avoid confusing other developers, you should implement them with those meanings in mind. You also might wish to implement Close() in situations in which a Close() method has been traditional programming practice or fits in with traditional terminology, such as closing a file or database connection. A case in which you might use Dispose() might be to release handles to various GDI or other Windows objects. You will also need to use Dispose() if you wish to take advantage of the IDisposable architecture, which we discuss next.

Using the IDisposable Interface

C# offers a syntax that you can use to guarantee that Dispose() (though not Close() ) will automatically be called against an object when the reference goes out of scope. The syntax to do this involves the using keyword - though now in a very different context, which has nothing to do with namespaces. Suppose we have a class, let's call it ResourceGobbler , which relies on the use of some external resource, and we wish to instantiate an instance of this class. We could do it like this:

   {     ResourceGobbler theInstance = new ResourceGobbler();     // do your processing     theInstance.Dispose();     }   

I've put braces around the above code. These aren't necessary, but I've put them there to emphasize that, after calling theInstance.Dispose() , we have presumably finished with theInstance , so there's presumably no more point the reference remaining in scope. A better approach is to transfer the processing above into a using block:

   using (ResourceGobbler theInstance = new ResourceGobbler())     {     // do your processing     }   

The using block, followed in brackets by a reference variable definition, will cause that variable to be scoped to the accompanying compound statement. In addition, when that variable goes out of scope, its Dispose() method will automatically be called.

The using syntax does have the disadvantage that it forces extra indentation of the code, and it doesn't save you very much coding. We will also see in Chapter 4, when we examine exceptions, that a similar effect can alternatively be achieved anyway by placing the call to Dispose() in a finally block. If you are using exceptions anyway, this will likely be the preferred technique. For these reasons, you may prefer to avoid the using construct.

In order to use the alternative using syntax, we do have to define the ResourceGobbler class in a certain way. In order to see what this involves, we're going to have to jump ahead of ourselves a bit, since it relies on use of interfaces. The restriction is that we have to derive ResourceGobbler from an interface called IDisposable , which is defined in the System namespace:

   class ResourceGobbler : IDisposable     {     // etc.     public void Dispose()     {     // etc.     }     }   

Deriving from an interface is a bit different from deriving from a class. We'll examine the details later in the chapter. We'll just say here that deriving from IDisposable has the effect of forcing the derived class to implement a method called Dispose(); you'll actually get a compilation error if you derive from IDisposable and don't implement this method. The reason that we have to derive from IDisposable in order to use the using syntax is that it gives the compiler a way of checking that the object defined in the using statement does have a Dispose() method that it can automatically call. As we'll see later in the chapter, deriving from an interface is the usual way that a class can declare that it implements certain features.

Implementing Destructors and Dispose()

Now we've had a look at the theory we'll expand out the above code to see how it works in practice. First, here's an example of a class that contains only a managed resource.

   public class ManagedResourceGobbler : IDisposable     {     private StreamReader sr;     public void Dispose()     {     if (sr != null)     {     sr.Close();     sr = null;     }     }     }   

ManagedResourceGobbler doesn't contain any direct references to unmanaged resources, which means it doesn't need a destructor. However, it does contain a reference to a StreamReader object. We'll meet StreamReader in the chapter on file handling. It's a class defined in the System.IO namespace and is designed to read data from, for example, a file. Since StreamReader will internally hold an external resource, it's a fair guess that it will implement the destructor-dispose paradigm we've just seen. It certainly does have a Dispose() method, and if we are following good programming practices we will want to ensure that it gets called as soon as possible. Hence we implement a Dispose() method that calls the StreamReader 's Close() . That's what the above code illustrates. Notice that, because we don't have a destructor, we can keep all our cleanup code in our public Dispose() method.

Next let's look at the situation for a class that has references to managed and unmanaged resources.

   public class ResourceGobbler : IDisposable     {     private StreamReader sr;     private int connection;     public void Dispose()     {     Dispose(true);     GC.SuppressFinalize(this);     }     protected virtual void Dispose(bool disposing)     {     if (disposing)     {     if (sr != null)     {     sr.Close();     sr = null;     }     }     CloseConnection();     }     ~ResourceGobbler()     {     Dispose (false);     }     void CloseConnection()     {     // code here will close connection     }     }   

The ResourceGobbler class has a reference to a StreamReader . However, it also has an int called connection , which we will assume somehow represents some unmanaged object. This means we will need to implement a destructor. In the above code we assume that CloseConnection() is a method that closes the external resource.

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

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