Recipe9.5.Wrapping Sealed Classes to Add Events


Recipe 9.5. Wrapping Sealed Classes to Add Events

Problem

Through the use of inheritance, adding events to a nonsealed class is fairly easy. For example, inheritance is used to add events to a Hashtable object. However, adding events to a sealed class, such as System.IO.DirectoryInfo, requires a technique other than inheritance.

Solution

To add events to a sealed class, such as the DirectoryInfo class, wrap it using another class, such as the DirectoryInfoNotify class defined in Example 9-3.

You can use the FileSystemWatcher class (see Recipes 12.23 and 12.24) to monitor the filesystem changes asynchronously due to activity outside of your program, or you can use the DirectoryInfoNotify class defined here to monitor your program's activity when using the filesystem.


Example 9-3. Adding events to a sealed class

 using System;  using System.IO; public class DirectoryInfoNotify {     public DirectoryInfoNotify(string path)     {         internalDirInfo = new DirectoryInfo(path);     }     private DirectoryInfo internalDirInfo = null;     public event EventHandler AfterCreate;     public event EventHandler AfterCreateSubDir;     public event EventHandler AfterDelete;     public event EventHandler AfterMoveTo;     protected virtual void OnAfterCreate( )     {         EventHandler afterCreate = AfterCreate;         if (afterCreate!= null)         {             afterCreate (this, new EventArgs( ));         }     }          protected virtual void OnAfterCreateSubDir( )     {         EventHandler afterCreateSubDir = AfterCreateSubDir;         if (afterCreateSubDir != null)         {             afterCreateSubDir(this, new EventArgs( ));         }     }     protected virtual void OnAfterDelete( )     {         EventHandler afterDelete = AfterDelete;         if (afterDelete != null)         {             afterDelete(this, new EventArgs( ));         }     }     protected virtual void OnAfterMoveTo( )     {         EventHandler afterMoveTo = AfterMoveTo;         if (afterMoveTo != null)         {             afterMoveTo(this, new EventArgs( ));         }     }     // Event firing members     public void Create( )     {         internalDirInfo.Create( );         OnAfterCreate( );     }     public DirectoryInfoNotify CreateSubdirectory(string path)     {          DirectoryInfo subDirInfo = internalDirInfo.CreateSubdirectory(path);          OnAfterCreateSubDir( );         return (new DirectoryInfoNotify(subDirInfo.FullName));     }     public void Delete(bool recursive)     {         internalDirInfo.Delete(recursive);         OnAfterDelete( );     }     public void Delete( )     {         internalDirInfo.Delete( );         OnAfterDelete( );     }     public void MoveTo(string destDirName)     {         internalDirInfo.MoveTo(destDirName);         OnAfterMoveTo( );     }          // Nonevent firing members     public string FullName     {         get {return (internalDirInfo.FullName);}     }     public string Name     {         get {return (internalDirInfo.Name);}     }     public DirectoryInfoNotify Parent     {         get {return (new DirectoryInfoNotify(internalDirInfo.Parent.FullName));}     }     public DirectoryInfoNotify Root     {         get {return (new DirectoryInfoNotify(internalDirInfo.Root.FullName));}     }          public override string ToString( )      {          return (internalDirInfo.ToString( ));      }  } 

The DirectoryInfoObserver class, shown in Example 9-4, allows you to register any DirectoryInfoNotify objects with it. This registration process allows the DirectoryInfoObserver class to listen for any events to be raised in the registered DirectoryInfoNotify object(s). The only events that are raised by the DirectoryInfoNotify object are after a modification has been made to the directory structure using a DirectoryInfoNotify object that has been registered with a DirectoryInfoObserver object.

Example 9-4. DirectoryInfoObserver class

 public class DirectoryInfoObserver  {      public DirectoryInfoObserver( ) {}     public void Register(DirectoryInfoNotify dirInfo)     {         dirInfo.AfterCreate += new EventHandler(AfterCreateListener);         dirInfo.AfterCreateSubDir +=                 new EventHandler(AfterCreateSubDirListener);         dirInfo.AfterMoveTo += new EventHandler(AfterMoveToListener);         dirInfo.AfterDelete += new EventHandler(AfterDeleteListener);     }     public void UnRegister(DirectoryInfoNotify dirInfo)     {         dirInfo.AfterCreate -= new EventHandler(AfterCreateListener);         dirInfo.AfterCreateSubDir -=                 new EventHandler(AfterCreateSubDirListener);         dirInfo.AfterMoveTo -= new EventHandler(AfterMoveToListener);         dirInfo.AfterDelete -= new EventHandler(AfterDeleteListener);     }     public void AfterCreateListener(object sender, EventArgs e)     {         Console.WriteLine("Notified after creation of directory--sender: " +                            ((DirectoryInfoNotify)sender).FullName);     }     public void AfterCreateSubDirListener(object sender, EventArgs e)     {         Console.WriteLine("Notified after creation of SUB-directory--sender: " +                            ((DirectoryInfoNotify)sender).FullName);     }     public void AfterMoveToListener(object sender, EventArgs e)     {         Console.WriteLine("Notified of directory move--sender: " +                            ((DirectoryInfoNotify)sender).FullName);     }     public void AfterDeleteListener(object sender, EventArgs e)      {          Console.WriteLine("Notified of directory deletion--sender: " +                             ((DirectoryInfoNotify)sender).FullName);      }  } 

Discussion

Wrapping is a very useful technique with many different applications (proxies, facades, etc.). However, if you use it, all classes in your application have to use the wrapped version of the class, or your wrapper code will not execute for cases when the sealed class is used directly.

In some situations this technique might be useful even when a class is not sealed. For example, if you want to raise notifications when methods that have not been declared as virtual are called, you'll need this technique to wrap those methods and supply the notifications. So even if DirectoryInfo were not sealed, you would still need this technique because you can't override its Delete, Create, and other methods. And hiding them with the new keyword is unreliable because someone might use your object through a reference of type DirectoryInfo instead of type DirectoryInfoNotify, in which case the original methods and not your new methods will be used. So the delegation approach presented here is the only reliable technique when methods in the base class are not virtual methods, regardless of whether the base class is sealed.

The TestDirectoryInfoObserver method shown in Example 9-5 creates two DirectoryInfoObserver objects along with two DirectoryInfoNotify objects, and then it proceeds to create a directory, C:\testdir, and a subdirectory under C:\testdir called new.

Example 9-5. TestDirectoryInfoObserver method

 public void TestDirectoryInfoObserver( ) {     // Create two observer objects.     DirectoryInfoObserver observer1 = new DirectoryInfoObserver( );     DirectoryInfoObserver observer2 = new DirectoryInfoObserver( );     // Create a notification object for the directory c:\testdir.     DirectoryInfoNotify dirInfo = new DirectoryInfoNotify(@"c:\testdir");     // Register the notification object under both observers.     observer1.Register(dirInfo);     observer2.Register(dirInfo);     // Create the directory c:\testdir.     dirInfo.Create( );     // Have the first observer watch the new subdirectory as well.     DirectoryInfoNotify subDirInfo = dirInfo.CreateSubdirectory("new");     observer1.Register(subDirInfo);     // Delete the subdirectory first and then the parent directory.     subDirInfo.Delete(true);     dirInfo.Delete(false);     // Unregister notification objects with their observers.     observer2.UnRegister(dirInfo);     observer1.UnRegister(dirInfo);  } 

This code outputs the following:

 Notified after creation of directory--sender: c:\testdir Notified after creation of directory--sender: c:\testdir Notified after creation of SUB-directory--sender: c:\testdir Notified after creation of SUB-directory--sender: c:\testdir Notified of directory deletion--sender: c:\testdir\new Notified of directory deletion--sender: c:\testdir Notified of directory deletion--sender: c:\testdir 

Rather than using inheritance to override members of a sealed class (i.e., the DirectoryInfo class), the sealed class is wrapped by a notification class (i.e., the DirectoryInfoNotify class).

The main drawback to wrapping a sealed class is that each method available in the underlying DirectoryInfo class might have to be implemented in the outer DirectoryInfoNotify class, which can be tedious if the underlying class has many visible members. The good news is that if you know you will not be using a subset of the wrapped class's members, you do not have to wrap each of those members. Simply do not make them visible from your outer class, which is what you have done in the DirectoryInfoNotify class. Only the methods you intend to use are implemented on the DirectoryInfoNotify class. If more methods on the DirectoryInfo class will later be used from the DirectoryInfoNotify class, they can be added with minimal effort.

For a DirectoryInfoNotify object to wrap a DirectoryInfo object, the DirectoryInfoNotify object must have an internal reference to the wrapped DirectoryInfo object. This reference is in the form of the internalDirInfo field. Essentially, this field allows all wrapped methods to forward their calls to the underlying DirectoryInfo object. For example, the Delete method of a DirectoryInfoNotify object forwards its call to the underlying DirectoryInfo object as follows:

 public void Delete( ) {     // Forward the call.     internalDirInfo.Delete( );          // Raise an event.     OnAfterDelete( ); } 

You should make sure that the method signatures are the same on the outer class as they are on the wrapped class. This convention will make it much more intuitive and transparent for another developer to use. You could also make it completely different to differentiate the wrapper from the contained class. The key is not to have it look very similar to the contained class but with slight differences, as that would be the most confusing for your consumers.

There is one method, CreateSubdirectory, that requires further explanation:

 public DirectoryInfoNotify CreateSubdirectory(string path) {     DirectoryInfo subDirInfo = internalDirInfo.CreateSubdirectory(path);     OnAfterCreateSubDir( );     return (new DirectoryInfoNotify(subDirInfo.FullName));  }  

This method is unique since it returns a DirectoryInfo object in the wrapped class. However, if you also returned a DirectoryInfo object from this outer method, you might confuse the developer attempting to use the DirectoryInfoNotify class. If a developer is using the DirectoryInfoNotify class, she will expect that class to also return objects of the same type from the appropriate members rather than returning the type of the wrapped class.

To fix this problem and make the DirectoryInfoNotify class more consistent, a DirectoryInfoNotify object is returned from the CreateSubdirectory method. The code that receives this DirectoryInfoNotify object might then register it with any available DirectoryInfoObserver object(s). This technique is shown here:

 // Create a DirectoryInfoObserver object and a DirectoryInfoNotify object. DirectoryInfoObserver observer = new DirectoryInfoObserver( ); DirectoryInfoNotify dirInfo = new DirectoryInfoNotify(@"c:\testdir"); // Register the DirectoryInfoNotify object with the DirectoryInfoObserver object. observer.Register(dirInfo); // Create the c:\testdir directory and then create a subdirectory within that // directory; this will return a new DirectoryInfoNotify object, which is // registered with the same DirectoryInfoObserver object as the dirInfo object. dirInfo.Create( ); DirectoryInfoNotify subDirInfo = dirInfo.CreateSubdirectory("new"); observer.Register(subDirInfo); // Delete this subdirectory. subDirInfo.Delete(true); // Clean up. observer.UnRegister(dirInfo); 

The observer object will be notified of the following events in this order:

  1. When the dirInfo.Create method is called

  2. When the dirInfo.CreateSubdirectory method is called

  3. When the subDirInfo.Delete method is called

If the second observer.Register method were not called, the third event (subDirInfo.Delete) would not be caught by the observer object.

The DirectoryInfoObserver class contains methods that listen for events on any DirectoryInfoNotify objects that are registered with it. The XxxListener methods are called whenever their respective event is raised on a registered DirectoryInfoNotify object. Within these XxxListener methods, you can place any code that you wish to execute whenever a particular event is raised.

These XxxListener methods accept a sender object parameter, which is a reference to the DirectoryInfoNotify object that raised the event. This sender object can be cast to a DirectoryInfoNotify object and its members may be called if needed. This parameter allows you to gather information and take action based on the object that raised the event.

The second parameter to the XxxListener methods is of type EventArgs, which is a rather useless class for your purposes. Recipe 9.6 shows a way to use a class derived from the EventArgs class to pass information from the object that raised the event to the XxxListener method on the observer object and then back to the object that raised the event.

See Also

See Recipe 9.6; see the "Event Keyword" and "Handling and Raising Events" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

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