Monitor your collection

There are times when you want your class to expose some kind of collection. Maybe you got a Server class and want to world to access it connections, or you got a Family class and want to provide access to the collection of family members. Sometimes that is easy - just to return a reference to the collection from a function or property. Other times it’s not that easy.

Let say that you want expose a collection, but you don’t want anybody to modify that collection. There are different ways to solve that. You could return a clone of your collection, or maybe convert it into an array.

But what if you want to allow users to modify your collection, but still keep an eye on the actions taken. Maybe you want to restrict specific persons from being added to a a family, or maybe you want to temporarily suspend a maintenance job, while someone is trying to modify your connection collection… the list could go on and on.
A typically solution is to create a specialized collection, but that is often more work than necessary. Another way its to creaet a more generic reusable collection ‘wrapper’ that is capable of thrown event when actions (add, remove, clear) are performed. Here is an example of such a collection:

namespace NotifyingCollection
{
    using System.Collections.Generic;     

    public class NotifyingCollectionEventArgs
    {
        public T Item;
        public bool Cancel; // only valid for OnBeforeXXX events     

        public NotifyingCollectionEventArgs(T item)
        {
            this.Item = item;
            this.Cancel = false;
        }
    }     

    public class NotifyingCollection : ICollection
    {
        public delegate void NotifyingCollectionDelegate(
            NotifyingCollection source,
            NotifyingCollectionEventArgs ev);     

        public event NotifyingCollectionDelegate OnBeforeAdd;
        public event NotifyingCollectionDelegate OnAfterAdd;
        public event NotifyingCollectionDelegate OnBeforeRemove;
        public event NotifyingCollectionDelegate OnAfterRemove;
        public event NotifyingCollectionDelegate OnBeforeClear;
        public event NotifyingCollectionDelegate OnAfterClear;     

        private ICollection  m_pInnerCollection;     

        public NotifyingCollection(ICollection pInner)
        {
            m_pInnerCollection = pInner;
        }     

        public void  Add(T item)
        {
            NotifyingCollectionEventArgs ev =
                new NotifyingCollectionEventArgs(item);     

            // Notify listener that we’re about to add an item
            if (OnBeforeAdd != null)
                OnBeforeAdd(this, ev);     

            // Was the action canceled?
            if (ev.Cancel)
                return;      

            // Add item
            m_pInnerCollection.Add(ev.Item);     

            // Notify listened that we’ve added the item
            if (OnAfterAdd != null)
                OnAfterAdd(this, ev);
        }     

        public void  Clear()
        {
            NotifyingCollectionEventArgs ev =
                new NotifyingCollectionEventArgs(default(T));     

            // Notify listener that we’re about to clear the collection
            if (OnBeforeClear != null)
                OnBeforeClear(this, ev);     

            // Was the action canceled?
            if (ev.Cancel)
                return;     

            // Clear the list
            m_pInnerCollection.Clear();     

            // Notify listened that we’ve cleared the collection
            if (OnAfterClear != null)
                OnAfterClear(this, ev);
        }     

        public bool Remove(T item)
        {
            NotifyingCollectionEventArgs ev =
                new NotifyingCollectionEventArgs(item);     

            // Notify listener that we’re about to remove an item
            if (OnBeforeRemove != null)
                OnBeforeRemove(this, ev);     

            // Was the action canceled?
            if (ev.Cancel)
                return false;     

            // Remove the item
            bool bRet = m_pInnerCollection.Remove(ev.Item);     

            // Notify listener that we’ve removed an item
            if (OnAfterRemove != null)
                OnAfterRemove(this, ev);     

            return bRet;
        }     

        #region Thin Wrappers     

        public bool  Contains(T item)
        {
 	        return m_pInnerCollection.Contains(item);
        }     

        public void  CopyTo(T[] array, int arrayIndex)
        {
 	        m_pInnerCollection.CopyTo(array, arrayIndex);
        }     

        public int  Count
        {
	        get
            {
                return m_pInnerCollection.Count;
            }
        }     

        public bool  IsReadOnly
        {
	        get
            {
                return m_pInnerCollection.IsReadOnly;
            }
        }     

        public IEnumerator  GetEnumerator()
        {
            return m_pInnerCollection.GetEnumerator();
        }     

        System.Collections.IEnumerator  System.Collections.IEnumerable.GetEnumerator()
        {
            return m_pInnerCollection.GetEnumerator();
        }     

        #endregion
    }
}

And here’s a small example utilizing our generic event throwing collection. The samples consist of an Test class that exposes a collection of integers through a public property. The OnBeforeAdd event is handled to cancel any attempt to add 24 the list (24 is just for demonstration purposes… you could do whatever you like):

namespace NotifyingCollectionTest
{
    using System.Collections.Generic;
    using NotifyingCollection;     

    public class Test
    {
        private List intList;
        private NotifyingCollection notifyingIntList;     

        public Test()
        {
            intList = new List();
            notifyingIntList = new NotifyingCollection(intList);
            notifyingIntList.OnBeforeAdd += notifyingIntList_OnBeforeAdd;
        }     

        public ICollection IntList
        {
            get
            {
                return notifyingIntList;
            }
        }     

        void notifyingIntList_OnBeforeAdd(NotifyingCollection source,
            NotifyingCollectionEventArgs ev)
        {
            // don’t allow any ‘24′ into our list
            if (ev.Item == 24)
                ev.Cancel = true;
        }
    }     

    class Program
    {
        static void Main(string[] args)
        {
            Test pTest = new Test();
            pTest.IntList.Add(23); // add 23 to list
            pTest.IntList.Add(24); // add 24 to list
            pTest.IntList.Add(25); // add 25 to list     

            return;
        }
    }
}

Leave a comment...

Powered by WordPress. Entries (RSS) and Comments (RSS).