Recipe9.9.Observing Additions and Modifications to a Hashtable


Recipe 9.9. Observing Additions and Modifications to a Hashtable

Problem

You have multiple objects that need to observe modifications to a Hashtable. When an item is added or modified in the Hashtable, each of these observer objects should be able to vote to allow or disallow the action. In order for an action to be allowed to complete, all observer objects must vote to allow the action. If even one observer object votes to disallow the action, the action is prevented.

Solution

Use the HashtableObserver class implemented in Example 9-11 to observe additions and modifications to the ObservableHashtable class (shown in Example 9-10) object that is registered with this object. The ObservableHashtable class is an extension of the regular Hashtable class and allows itself to be observed by the HashtableObserver class.

Example 9-10. ObservableHashtable class

 public class ObservableHashtable : Hashtable {     public event HashtableEventHandler BeforeAddItem;     public event HashtableEventHandler AfterAddItem;     public event HashtableEventHandler BeforeChangeItem;     public event HashtableEventHandler AfterChangeItem;          protected virtual bool OnBeforeAdd(HashtableEventArgs e)     {         HashtableEventHandler beforeAddItem = BeforeAddItem;         if (beforeAddItem != null)         {             beforeAddItem(this, e);             return (e.KeepChanges);         }         return (true);     }     protected virtual void OnAfterAdd(HashtableEventArgs e)     {         HashtableEventHandler afterAddItem = AfterAddItem;         if (afterAddItem != null)         {             afterAddItem(this, e);         }     }     protected virtual bool OnBeforeChange(HashtableEventArgs e)     {         HashtableEventHandler beforeChangeItem = BeforeChangeItem;         if (beforeChangeItem != null)         {             beforeChangeItem(this, e);             return (e.KeepChanges);         }                  return (true);     }     protected virtual void OnAfterChange(HashtableEventArgs e)     {         HashtableEventHandler afterChangeItem = AfterChangeItem;         if (afterChangeItem != null)         {             afterChangeItem(this, e);         }     }     public override void Add(object key, object value)     {         HashtableEventArgs hashArgs =             new HashtableEventArgs(key, value);         if(OnBeforeAdd(hashArgs))         {             base.Add(key, value);         }         else         {             Debug.WriteLine("Addition of key/value cannot be performed");         }                   OnAfterAdd(hashArgs);     }      public override object this[object key]     {         get         {             return (base[key]);         }         set         {             // See if this key is there to be changed; if not, add it.             if (base.ContainsKey(key))             {                 HashtableEventArgs hashArgs = new HashtableEventArgs(key, value);                 if (OnBeforeChange(hashArgs))                 {                     base[key] = value;                 }                 else                 {                     Debug.WriteLine("Change of value cannot be performed");                 }                 OnAfterChange(hashArgs);             }             else             {                 Debug.WriteLine("Item did not exist, adding");                 Add(key, value);             }         }     } } 

The HashtableEventHandler is defined as follows:

 [Serializable] public delegate void HashtableEventHandler(object sender,                                 HashtableEventArgs args); 

Example 9-11 shows the code for the HashtableObserver class.

Example 9-11. HashtableObserver class

 // The observer object that will observe a registered // ObservableHashtable object public class HashtableObserver {     public HashtableObserver() {}     // Set up delegate/events for approving an addition or change.     public delegate bool Approval(HashtableEventArgs args);     public event Approval ApproveAdd;     public event Approval ApproveChange;     public void Register(ObservableHashtable hashtable)     {         // Hook up to the ObservableHashTable instance events.         hashtable.BeforeAddItem +=             new HashtableEventHandler(BeforeAddListener);         hashtable.AfterAddItem +=             new HashtableEventHandler(AfterAddListener);         hashtable.BeforeChangeItem +=             new HashtableEventHandler(BeforeChangeListener);         hashtable.AfterChangeItem +=             new HashtableEventHandler(AfterChangeListener);     }     public void Unregister(ObservableHashtable hashtable)     {         // Unhook from the ObservableHashTable instance events.         hashtable.BeforeAddItem -=             new HashtableEventHandler(BeforeAddListener);         hashtable.AfterAddItem -=             new HashtableEventHandler(AfterAddListener);         hashtable.BeforeChangeItem -=             new HashtableEventHandler(BeforeChangeListener);         hashtable.AfterChangeItem -=             new HashtableEventHandler(AfterChangeListener);     }     private void CheckApproval(Approval approval,                         HashtableEventArgs args)     {         // Check everyone who wants to approve.         foreach (Approval approvalInstance in approval.GetInvocationList())         {             if (!approvalInstance(args))             {                 // If any of the concerned parties                 // refuse, then no add. Adds by default.                 args.KeepChanges = false;                 break;             }         }     }     public void BeforeAddListener(object sender, HashtableEventArgs args)     {         // See if anyone is hooked up for approval.         Approve approveAdd = ApproveAdd;         if (approveAdd != null)         {             CheckApproval(approveAdd, args);         }         Debug.WriteLine("[NOTIFY] Before Add…: Add Approval = " +                                         args.KeepChanges.ToString());     }     public void AfterAddListener(object sender, HashtableEventArgs args)     {         Debug.WriteLine("[NOTIFY] …After Add: Item approved for adding: " +                             args.KeepChanges.ToString( ));     }     public void BeforeChangeListener(object sender, HashtableEventArgs args)     {         // See if anyone is hooked up for approval.         Approve approveChange = ApproveChange;         if (approveChange != null)         {             CheckApproval(approveChange, args);         }         Debug.WriteLine("[NOTIFY] Before Change…: Change Approval = " +                             args.KeepChanges.ToString());     }     public void AfterChangeListener(object sender, HashtableEventArgs args)     {         Debug.WriteLine("[NOTIFY] …After Change: Item approved for change: " +                             args.KeepChanges.ToString());     } } 

The HashtableEventArgs class is a specialization of the EventArgs class, which provides the Hashtable key and value being added or modified to the HashtableObserver object, as well as a Boolean property, KeepChanges. This flag indicates whether the addition or modification in the ObservableHashtable object will succeed or be rolled back. The source code for the HashtableEventArgs class is:

 // Event arguments for ObservableHashtable public class HashtableEventArgs : EventArgs {     public HashtableEventArgs(object key, object value)     {         this.key = key;         this.value = value;     }     private object key = null;     private object value = null;     private bool keepChanges = true;          public bool KeepChanges     {         get {return (keepChanges);}         set {keepChanges = value;}     }     public object Key     {         get {return (key);}     }     public object Value     {         get {return (value);}     } } 

Discussion

The observer design pattern allows one or more observer objects to act as spectators over one or more subjects. Not only do the observer objects act as spectators, but they can also induce change in the subjects. According to this pattern, any subject is allowed to register itself with one or more observer objects. Once this is done, the subject can operate as it normally does. The key feature is that the subject doesn't have to know what it is being observed bythis allows the coupling between subjects and observers to be minimized. The observer object(s) will then be notified of any changes in state to the subjects. When the subject's state changes, the observer object(s) can change the state of other objects in the system to bring them into line with changes that were made to the subject(s). In addition, the observer could even make changes or refuse changes to the subject(s) themselves.

The observer pattern is best implemented with events in C#. The event object provides a built-in way of implementing the observer design pattern. This recipe implements this pattern on a Hashtable. The Hashtable object must raise events for any listening observer objects to handle. But the Hashtable class found in the FCL does not raise any events. In order to make a Hashtable raise events at specific times, you must derive a new class, ObservableHashtable, from the Hashtable class. This ObservableHashtable class overrides the Add and indexer members of the base Hashtable. In addition, four events (BeforeAddItem, AfterAddItem, BeforeChangeItem, and AfterChangeItem) are created; they will be raised before and after items are added or modified in the ObservableHashtable object. To raise these events, the following four methods are created, one to raise each event:

  • The OnBeforeAdd method raises the BeforeAddItem event.

  • The OnAfterAdd method raises the AfterAddItem event.

  • The OnBeforeChange method raises the BeforeChangeItem event.

  • The OnAfterChange method raises the AfterChangeItem event.

The Add method calls the OnBeforeAdd method, which then raises the event to any listening observer objects. The OnBeforeAdd method is called before the base.Add methodwhich adds the key/value pair to the Hashtableis called. After the key/ value pair has been added, the OnAfterAdd method is called. This operation is similar to the indexer set method.

The Onxxx methods that raise the events in the ObservableHashtable class are marked as protected virtual to allow classes to subclass this class and implement their own method of dealing with the events. Note that this statement is not applicable to sealed classes. In those cases, you can simply make the methods public.


The HashtableEventArgs class contains three private fields defined as follows:


key

The key that is to be added to the Hashtable.


value

The value that is to be added to the Hashtable.


keepChanges

A flag indicating whether the key/value pair should be added to the Hashtable. true indicates that this pair should be added to the Hashtable.

The keepChanges field is used by the observer to determine whether an add or change operation should proceed. This flag is discussed further when you look at the HashtableObserver observer object.

The HashtableObserver is the observer object that watches any ObservableHashtable objects it is told about. Any ObservableHashtable object can be passed to the HashtableObserver.Register method in order to be observed. This method accepts an ObservableHashtable object (hashtable) as its only parameter. This method then hooks up the event handlers in the HashtableObserver object to the events that can be raised by the ObservableHashtable object passed in through the hashtable parameter. Therefore, the following events and event handlers are bound together:

  • The ObservableHashtable.BeforeAddItem event is bound to the HashtableObserver.BeforeAddListener event handler.

  • The ObservableHashtable.AfterAddItem event is bound to the HashtableObserver.AfterAddListener event handler.

  • The ObservableHashtable.BeforeChangeItem event is bound to the HashtableObserver.BeforeChangeListener event handler.

  • The ObservableHashtable.AfterChangeItem event is bound to the HashtableObserver.AfterChangeListener event handler.

The BeforeAddListener and BeforeChangeListener methods watch for additions and changes to the key/value pairs of the watched ObservableHashtable object(s). Since you have an event firing before and after an addition or modification occurs, you can determine whether the addition or change should occur.

Two events are published by the HashtableObserver to allow for an external entity to approve or deny the addition or changing of a hashtable entry. These events are named ApproveAdd and ApproveChange, respectively, and are of delegate type Approval as shown below.

 public delegate bool Approval(HashtableEventArgs args); 

This is where the keepChanges field of the HashtableEventArgs object comes into play. If an external source wants to block the addition or change, it can simply return false from its event handler implementation of the appropriate Approve* event.

The HashtableObserver object will set this flag according to whether it determines that the action should proceed or be prematurely terminated. The HashtableEventArgs object is passed back to the OnBeforeAdd and OnBeforeChange methods. These methods then return the value of the KeepChanges property to either the calling Add method or indexer. The Add method or indexer then uses this flag to determine whether the base Hashtable object should be updated.

The code in Example 9-12 shows how to instantiate ObservableHashtables and HashtableObservers, and how to register, set up approval, use, and unregister them.

Example 9-12. Using the ObservableHashTable and HashTableObserver classes

 public static void TestObserverPattern() {     // Create three observable hashtable instances.     ObservableHashtable oh1 = new ObservableHashtable();     ObservableHashtable oh2 = new ObservableHashtable();     ObservableHashtable oh3 = new ObservableHashtable();     // Create an observer for the three subject objects.     HashtableObserver observer = new HashtableObserver();     // Register the three subjects with the observer.     observer.Register(oh1);     observer.Register(oh2);     observer.Register(oh3);     // Hook up the approval events for adding or changing.     observer.ApproveAdd +=         new HashtableObserver.Approval(SeekApproval);         observer.ApproveChange +=             new HashtableObserver.Approval(SeekApproval);         // Use the observable instances.         oh1.Add(1,"one");         oh2.Add(2,"two");         oh3.Add(3,"three");         // Unregister the observable instances.         observer.Unregister(oh3);         observer.Unregister(oh2);         observer.Unregister(oh1);     }     public static bool SeekApproval(HashtableEventArgs args)     {         // Allow only strings of no more than 3 characters in         // our hashtable.         string value = (string)args.Value;         if (value.Length <= 3)             return true;         return false;     } 

Note that if the ObservableHashtables are used without registering them, no events will be raised. Since no events are raised, the observer cannot do its job, and values may be added to the unregistered subjects that are out of bounds for the application.

When using the observer design pattern in this fashion, keep in mind that fine-grained events, such as the ones in this recipe, could possibly drag down performance, so profile your code. If you have many subjects raising many events, your application could fail to meet performance expectations. If this occurs, you need to either minimize the number of actions that cause events to be raised or remove some events.

See Also

See the "Event Keyword," "EventHandler Delegate," "EventArgs Class," 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