Overriding Dispose(bool)


The version of the CipherComp component just shown does not hold any system resource, nor does it create and hold any objects. For these reasons, it was not necessary to override Dispose(bool). However, if your component does hold resources, then you will usually need to override Dispose(bool) so that the resources can be freed in a deterministic fashion. Fortunately, this is easy to do.

Before we begin, it is important to understand why a component will usually need to free its own resources, rather than relying on the normal C# garbage collection mechanism. As explained earlier in this book, as far as your program is concerned, garbage collection is a non-deterministic occurrence. It happens as needed (or when otherwise deemed appropriate by the garbage collector) and not just because objects are available to recycle. Thus, if a component holds a resource, such as an open file, which needs to be released in order for it to be used by another program, there must be some way to deterministically release these resources when a client is done using the component. Simply removing all references to the component does not solve the problem, because the component will still be holding a reference to the needed resource until the next time garbage is collected. The solution is for the component to implement Dispose(bool).

When overriding Dispose(bool), you must follow a few rules:

  1. When Dispose(bool) is called with a true argument, your version must release all resources, both managed and unmanaged, associated with the component. When it is called with a false argument, your version must release only the unmanaged resources, if any.

  2. Dispose(bool) must be able to be called repeatedly, without harm.

  3. Dispose(bool) must call the base class implementation of Dispose(bool).

  4. The destructor for your component should simply call Dispose(false).

To satisfy rule 2, your component will need to keep track of when it has been disposed. This is usually done by maintaining a private field that indicates the disposed status.

Here is a skeletal component that implements Dispose(bool):

 // A skeletal implementation of a component that uses Dispose(bool). class MyComp : Component {   bool isDisposed; // true if component is disposed   public MyComp() {     isDispose = false;     // ...   }   ~MyComp() {     Dispose(false);   }   protected override void Dispose(bool dispAll) {     if(!isDisposed) {       if(dispAll) {         // release managed resources here         isDisposed = true; // set component to disposed       }       // release unmanaged resources here       base.Dispose(dispAll);     }   } }

When you call Dispose( ) on an instance of a component, Dispose(bool) is automatically called to clean up any resources owned by the component.

Demonstrating Dispose(bool)

To illustrate Dispose(bool), we will enhance CipherComp so that it keeps a log of all encryption operations. To do this, it writes the result of each call to Encode( ) or Decode( ) to a file. This additional functionality is transparent to the user of CipherComp. It relies on Dispose(bool) to close the file when the component is no longer needed. Calls to WriteLine( ) are included to show when and how Dispose(bool) gets called.

 // An enhanced cipher component that maintains a log file. using System; using System.ComponentModel; using System.IO; namespace CipherLib {   // A Cipher component that maintains a log file.   public class CipherComp : Component {     static int useID = 0;     int id; // instance id     bool isDisposed; // true if component is disposed.     FileStream log;     // Constructor     public CipherComp() {       isDisposed = false; // component not disposed       try {         log = new FileStream("CipherLog" + useID, FileMode.Create);         id = useID;         useID++;       } catch (FileNotFoundException exc) {         Console.WriteLine(exc);         log = null;       }     }     // Destructor     ~CipherComp() {        Console.WriteLine("Destructor for component "                          + id);        Dispose(false);     }     // Encode the file. Return and store result.     public string Encode(string msg) {       string temp = "";       for(int i=0; i < msg.Length; i++)         temp += (char) (msg[i] + 1);       // Store in log file.       for(int i=0; i < temp.Length; i++)         log.WriteByte((byte) temp[i]);       return temp;     }     // Decode the message. Return and store result.     public string Decode(string msg) {       string temp = "";       for(int i=0; i < msg.Length; i++)         temp += (char) (msg[i] - 1);       // Store in log file.       for(int i=0; i < temp.Length; i++)         log.WriteByte((byte) temp[i]);       return temp;     }     protected override void Dispose(bool dispAll) {       Console.WriteLine("Dispose(" + dispAll +                         ") for component " + id);       if(!isDisposed) {         if(dispAll) {           Console.WriteLine("Closing file for " +                             "component " + id);           log.Close(); // close encoded file           isDisposed = true;         }         // no unmanaged resources to release         base.Dispose(dispAll);       }     }   } }

Let’s examine this version of CipherComp closely. It begins with these fields:

 static int useID = 0; int id; // instance id bool isDisposed; // true if component is disposed. FileStream log;

The first field is a static int that keeps a count of the number of instances of CipherComp that have been created. Its value will be used to create a unique identifier for each instance of CipherComp. The value in useID will also be incorporated into the log filename so that each instance of CipherComp writes to its own log file. The id field holds the ID of the component, which is the value of useID at the time the component was created. The isDisposed field indicates whether the component has been disposed. The fourth field is log, and it is a FileStream reference that will refer to the log file.

Next is CipherComp’s constructor, shown here:

 public CipherComp() {   isDisposed = false; // component not disposed   try {     log = new FileStream("CipherLog" + useID, FileMode.Create);     id = useID;     useID++;   } catch (FileNotFoundException exc) {     Console.WriteLine(exc);     log = null;   } }

In the constructor, isDisposed is initialized to false, which indicates that this CipherComp object is usable. Next, the log file is opened. Notice that the filename is a concatenation of “CipherLog” and the string representation of useID. Next, the value of useID is assigned to id and then useID is incremented. Thus, each instance of CipherComp uses a separate log file, and each component has a unique ID. An important point here is that creating a CipherComp object now opens a file, which is a system resource that must be released when the component is no longer needed. However, the client has no direct ability to release the file. In fact, the client does not even know that a file has been opened. Thus, closing the file must be handled by Dispose(bool).

Encode( ) encodes its string argument and returns the result. It also writes that result to the log file. Since log remains open, repeated calls to Encode( ) add output to the file. For example, using Encode( ) to encrypt three different strings results in a log file that contains all three encoded strings. Decode( ) works in the same way, except that it deciphers its argument.

Now, let’s look closely at Dispose(bool), which is overridden by CipherComp. It is shown here for your convenience:

 protected override void Dispose(bool dispAll) {   Console.WriteLine("Dispose(" + dispAll +                     ") for component " + id);   if(!isDisposed) {     if(dispAll) {       Console.WriteLine("Closing file for " +                         "component " + id);       log.Close(); // close encoded file       isDisposed = true;     }     // no unmanaged resources to release     base.Dispose(dispAll);   } }

Notice that Dispose(bool) is protected. This method is not to be called from client code. Rather, it is called by the publicly accessible Dispose( ) method, which is implemented by Component. Inside Dispose(bool), the value of isDisposed is checked. If the object has already been disposed, no action is taken. If it is false, the parameter dispAll is checked. If it is true, the log file is closed and isDisposed is set to true. Recall that, by convention, when dispAll is true, all resources are to be freed. When it is false, only the unmanaged resources (of which there are none in this case) are to be freed. Finally, Dispose(bool) as implemented by the base class (in this case Component) is called. This ensures that any resources used by the base class are released. The calls to WriteLine( ) are included only for the sake of illustration. A real-world Dispose(bool) method would not include them.

Now look at the destructor for CipherComp, shown here:

 ~CipherComp() {    Console.WriteLine("Destructor for component "                      + id);    Dispose(false); }

The destructor simply calls Dispose(bool) with a false argument. The reason for this is easy to understand. If the destructor for the component is being executed, the component is being recycled by the garbage collection. In this case, all managed resources will be automatically freed. The only thing that the destructor must do is release any unmanaged resources. The call to WriteLine( ) is made only for the sake of illustration and would not occur in a real program.

Because the changes to CipherComp do not affect its public interface, it can be used just as before. For example, here is a client program that encodes and decodes two strings:

 // Another client that uses CipherComp. using System; using CipherLib; // import CipherComp's namespace class CipherCompClient {   public static void Main() {     CipherComp cc = new CipherComp();     string text = "Testing";     string ciphertext = cc.Encode(text);     Console.WriteLine(ciphertext);     string plaintext = cc.Decode(ciphertext);     Console.WriteLine(plaintext);     text = "Components are powerful.";     ciphertext = cc.Encode(text);     Console.WriteLine(ciphertext);     plaintext = cc.Decode(ciphertext);     Console.WriteLine(plaintext);     cc.Dispose(); // free resources   } }

The output from the program is shown here:

 Uftujoh Testing Dpnqpofout!bsf!qpxfsgvm/ Components are powerful. Dispose(True) for component 0 Closing file for component 0

After the program runs, a log file called CipherLog0 will contain the following:

 UftujohTestingDpnqpofout!bsf!qpxfsgvm/Components are powerful.

This is the concatenation of the two strings that were encoded and decoded.

In the output, notice that Dispose(bool) is called with a true argument. This happens because the program calls Dispose( ) on the CipherComp object. As explained earlier, Dispose( ) then calls Dispose(bool) with a true argument, indicating that all resources are to be released. As an experiment, comment out the call to Dispose( ) in the client program, and then compile and run it. You will now see this output:

 Uftujoh Testing Dpnqpofout!bsf!qpxfsgvm/ Components are powerful. Destructor for component 0 Dispose(False) for component 0 Dispose(False) for component 0

Because no call to Dispose( ) was made, the CipherComp component is not explicitly disposed. It is, of course, destroyed when the program ends. Thus, as the output shows, its destructor is called, which in turn calls Dispose(bool) with a false argument. The second call to Dispose(bool) occurs because of the call to the base-class version of Dispose( ) inside CipherComp’s Dispose(bool) method. This causes Dispose(bool) to be called a second time. This is unnecessary in this case, but because Dispose( ) can’t know how it was called, it is unavoidable but harmless.

The same basic approach to implementing Dispose(bool) can be used by any component that you create.

Preventing a Disposed Component from Being Used

Although CipherComp just shown does properly dispose of itself, it still has one problem that must be addressed by any real-world component: It does not prevent a client from attempting to use a disposed component. For example, nothing stops a client from disposing of a CipherComp component and then attempting to call Encode( ) on it. In theory, this should not be an issue, because you should never use a component that has been disposed. Unfortunately, in large projects it is sometimes easy to forget that a component has already been disposed by another section of code. (This is especially troublesome in multithreaded applications.) Fortunately, there is also an easy remedy: The component must simply check the isDisposed field each time the component is used. For example, here are versions of Encode( ) and Decode( ) that confirm that the component has not been disposed:

 // Encode the file. Return and store result. public string Encode(string msg) {   // Prevent use of a disposed component.   if(isDisposed) {     Console.WriteLine("Error: Component disposed.");     return null;   }   string temp = "";   for(int i=0; i < msg.Length; i++)     temp += (char) (msg[i] + 1);   // Store in log file.   for(int i=0; i < temp.Length; i++)     log.WriteByte((byte) temp[i]);   return temp; } // Decode the message. Return and store result. public string Decode(string msg) {   // Prevent use of a disposed component.   if(isDisposed) {     Console.WriteLine("Error: Component disposed.");     return null;   }   string temp = "";   for(int i=0; i < msg.Length; i++)     temp += (char) (msg[i] - 1);   // Store in log file.   for(int i=0; i < temp.Length; i++)     log.WriteByte((byte) temp[i]);   return temp; }

In both methods, if isDisposed is true, an error message is displayed, and no other action is taken. Of course, in a real-world component, your code will normally throw an exception if an attempt is made to use a disposed object.




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

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