Recipe 9.9. Observing Additions and Modifications to a HashtableProblemYou 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. SolutionUse 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
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 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);} } } DiscussionThe 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 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 HashtableEventArgs class contains three private fields defined as follows:
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 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
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 AlsoSee the "Event Keyword," "EventHandler Delegate," "EventArgs Class," and "Handling and Raising Events" topics in the MSDN documentation. |