DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 7.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, deleted, 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 to observe additions and modifications to a HashtableSubject object that is registered with this object. The HashtableSubject class is an extension of the regular Hashtable class and allows itself to be observed by the HashtableObserver class. Its source code is:

public class HashtableSubject : Hashtable
{
    public event HashtableEventHandler BeforeAddItem;
    public event HashtableEventHandler AfterAddItem;
    public event HashtableEventHandler BeforeChangeItem;
    public event HashtableEventHandler AfterChangeItem;

    protected virtual bool OnBeforeAdd(HashtableEventArgs e)
    {
        if (BeforeAddItem != null)
        {
            BeforeAddItem(this, e);
            return (e.KeepChanges);
        }

        return (true);
    }

    protected virtual void OnAfterAdd(HashtableEventArgs e)
    {
        if (AfterAddItem != null)
        {
            AfterAddItem(this, e);
        }
    }

    protected virtual bool OnBeforeChange(HashtableEventArgs e)
    {
        if (BeforeChangeItem != null)
        {
            BeforeChangeItem(this, e);
            return (e.KeepChanges);
        }

        return (true);
    }

    protected virtual void OnAfterChange(HashtableEventArgs e)
    {
        if (AfterChangeItem != null)
        {
            AfterChangeItem(this, e);
        }
    }

    public override void Add(object key, object value)
    {
        HashtableEventArgs hashArgs = new HashtableEventArgs(key, value);
        OnBeforeAdd(hashArgs);

        if (hashArgs.KeepChanges)
        {
            base.Add(key, value);
        }
        else
        {
            Console.WriteLine("Addition of key/value cannot be performed");
        }

        OnAfterAdd(hashArgs);
    }

    public override object this[object key] 
    {
        get 
        {
            return (base[key]);
        }
        set 
        {
            HashtableEventArgs hashArgs = new HashtableEventArgs(key, value);
            OnBeforeChange(hashArgs);

            if (hashArgs.KeepChanges)
            {
                base[key] = value;
            }
            else
            {
                Console.WriteLine("Change of value cannot be performed");
            }

            OnAfterChange(hashArgs);
        }
    }
}

The HashtableEventHandler is defined as follows:

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

The code for the HashtableObserver class is:

using System;
using System.Collections;

// The observer object that will observe a registered HashtableSubject object
public class HashtableObserver
{
    public HashtableObserver( ) {}

    public void Register(HashtableSubject hashtable)
    {
        hashtable.BeforeAddItem += new HashtableEventHandler(BeforeAddListener);
        hashtable.AfterAddItem += new HashtableEventHandler(AfterAddListener);
        hashtable.BeforeChangeItem += 
            new HashtableEventHandler(BeforeChangeListener);
        hashtable.AfterChangeItem += 
            new HashtableEventHandler(AfterChangeListener);
    }

    public void UnRegister(HashtableSubject hashtable)
    {
        hashtable.BeforeAddItem -= new HashtableEventHandler(BeforeAddListener);
        hashtable.AfterAddItem -= new HashtableEventHandler(AfterAddListener);
        hashtable.BeforeChangeItem -= 
            new HashtableEventHandler(BeforeChangeListener);
        hashtable.AfterChangeItem -= 
            new HashtableEventHandler(AfterChangeListener);
    }

    public void BeforeAddListener(object sender, HashtableEventArgs e)
    {
        if (((string)e.Value).Length > 3)
        {
            e.KeepChanges = false;
        }
        else
        {
            e.KeepChanges = true;
        }

        Console.WriteLine("[NOTIFY] Before Add...");
    }

    public void AfterAddListener(object sender, HashtableEventArgs e)
    {
        Console.WriteLine("[NOTIFY] ...After Add\r\n");
    }

    public void BeforeChangeListener(object sender, HashtableEventArgs e)
    {
        if (((string)e.Value).Length > 3)
        {
            e.KeepChanges = false;
        }
        else
        {
            e.KeepChanges = true;
        }

        Console.WriteLine("[NOTIFY] Before Change...");
    }

    public void AfterChangeListener(object sender, HashtableEventArgs e)
    {
        Console.WriteLine("[NOTIFY] ...After Change\r\n");
    }
}

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 flag, KeepChanges, that's passed by reference. This flag indicates whether the addition or modification in the HashtableSubject object will succeed or be rolled back. The source code for the HashtableEventArgs class is:

// Event arguments for HashtableSubject
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 subject objects. Not only do the observer objects act as spectators, but they can also induce change in the subject objects. According to this pattern, any subject object 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 by—this 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 subject objects. When the subject object'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 object(s). In addition, the observer could even make changes or refuse changes to the subject object(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, we must derive a new class, HashtableSubject, from the Hashtable class. This HashtableSubject class overrides the Add and indexer members of the base Hashtable. In addition, four events (BeforeAddItem, AfterAddItem, BeforeChangeItem, and AfterChangeItem) are created that will be raised before and after items are added or modified in the HashtableSubject 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 method—which adds the key/value pair to the Hashtable—is 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 HashtableSubject 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 we look at the HashtableObserver observer object.

The HashtableObserver is the observer object that watches any HashtableSubject objects it is told about. Any HashtableSubject object can call the HashtableObserver.Register method in order to tell the HashtableObserver object that it wants to be observed. This method accepts a pointer to a HashtableSubject 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 HashtableSubject object passed in through the hashtable parameter. Therefore, the following events and event handlers are bound together:

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

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

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

  • The HashtableSubject.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 HashtableSubject object(s). Since we have an event firing before and after an addition or modification occurs, we can determine whether the addition or change should occur. This is where the keepChanges field of the HashtableEventArgs object comes into play. 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 following code shows how to instantiate subjects and observers, and to register, use, and unregister them:

// Create three subject objects
ObserverPattern.HashtableSubject H1 = 
   new ObserverPattern.HashtableSubject( );
ObserverPattern.HashtableSubject H2 = 
   new ObserverPattern.HashtableSubject( );
ObserverPattern.HashtableSubject H3 = 
   new ObserverPattern.HashtableSubject( );

// Create an observer for the three subject objects
ObserverPattern.HashtableObserver observer = 
   new ObserverPattern.HashtableObserver( );

// Register the three subjects with the observer
observer.Register(H1);
observer.Register(H2);
observer.Register(H3);

// Use the subjects
H1.Add(1,"one");
H2.Add(2,"two");
H3.Add(3,"three");

// Unregister the subjects
observer.UnRegister(H3);
observer.UnRegister(H2);
observer.UnRegister(H1);

Note that if the subject objects 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.

Many other scenarios exist in which the observer design pattern can be used. For example, if you wanted another Hashtable object to be updated to reflect the additions or modifications of a HashtableSubject object, you could modify the HashtableObserver object as shown in the highlighted text here:

public void Register(Hashtable hashtable)
{
    MirrorTable =  hashtable;
}

Hashtable MirrorTable = null;

public void BeforeAddListener(object sender, EventArgs e)
{
    HashtableEventArgs hashE = (HashtableEventArgs)e;

    if (((string)hashE.Value).Length > 3)
    {
        hashE.KeepChanges = false;
    }
    else
    {
        hashE.KeepChanges = true;
        if (MirrorTable != null)
        {
            MirrorTable.Add(hashE.Key, hashE.Value);
        }
    }

    Console.WriteLine("[NOTIFY] Before Add...");
}

public void BeforeChangeListener(object sender, EventArgs e)
{
    HashtableEventArgs hashE = (HashtableEventArgs)e;

    if (((string)hashE.Value).Length > 3)
    {
        hashE.KeepChanges = false;
    }
    else
    {
        hashE.KeepChanges = true;
        if (MirrorTable != null)
        {
            MirrorTable[hashE.Key] = hashE.Value;
        }
    }

    Console.WriteLine("[NOTIFY] Before Change...");
}

A new field, MirrorTable, has been added; it points to a Hashtable mirroring the observed HashtableSubject object. The MirrorTable object is set through the constructor of this class. The MirrorTable object is updated whenever the observed object is successfully modified. With these modifications to the HashtableObserver object, you should observe only one HashtableSubject object at any one time. If you are observing more than one subject object, you run the risk of attempting to add duplicate keys to the MirrorTable object.

When using the observer design pattern, you should keep in mind that fine-grained events, such as the ones in this recipe, should be watched carefully so that they do not drag down performance. 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.

    [ Team LiB ] Previous Section Next Section