Let's say we need to keep track of objects that have been instantiated in our application. The "Identity Map" pattern does just this and we can implement it with a simple Dictionary<TKey, TValue>. However, we need a different dictionary for each type of object which can become cumbersome to keep track of. Let's look at a technique by which we can have all of our pools in one aggregate pool object.
The trick is maintaining type-safety. At the gooey center of our aggregate pool we won't necessarily be type-safe, but everything on the crunchy exterior that is publicly available to consuming code will be. Let's say all of our object have a key property which is an Int32. If this is the case, we can just build a type-safe wrapper for a Dictionary<Type, Dictionary<Int32, Object>> where the interior Object is of the type of the key of the outer dictionary.
In order to keep things safe, we'll have to make the instance of the nested dictionary pool private. Then to safely add an item to the pool, we must first check if there is a "inner" dictionary before we execute the add method.
private Dictionary<Type, Dictionary<Int32, Object>> m_pool;
public void AddItem<T>(Int32 pID, T value)
{
Type myType = typeof(T);
if (!m_pool.ContainsKey(myType))
{
m_pool.Add(myType, new Dictionary<int, object>());
m_pool[myType].Add(pID, value);
return;
}
if (!m_pool[myType].ContainsKey(pID))
{
m_pool[myType].Add(pID, value);
return;
}
m_pool[myType][pID] = value;
}
When we remove an object from the pool, we'll have to again check that the "inner" dictionary exists:
public bool RemoveItem<T>(Int32 pID)
{
Type myType = typeof(T);
if (!m_pool.ContainsKey(myType))
return false;
if (!m_pool[myType].ContainsKey(pID))
return false;
return m_pool[myType].Remove(pID);
}
If these two methods are the only way to add and remove items from the pool then we can be sure of the type of the object in the inner Dictionary<Int32, Object>. This allows us to have one identity map object for all our loaded objects. So we can have on method to retrieve any object in the pool:
public T GetItem<T>(Int32 pID)
{
// will throw KeyNotFoundException if either of the dictionaries
// does not hold the required key
return (T) m_pool[myType][pID];
}
The thing to be careful of is if you have a long inheritance chains in your object model, you could enter an object in the pool under many different "type" keys and not know how to retrieve the objects put into your dictionary. Being aware of this, you have to be sure to use consistent types with each method call.
Here is how we would use our aggregate pool:
class Program
{
public static ObjectPool pool = new ObjectPool();
static void Main(string[] args)
{
Animal dog = new Animal() { Name = "Fido", ID = 1 };
Vegetable carrot = new Vegetable { Color = "Orange", Identifier = 1, IsTasty = true };
Mineral carbon = new Mineral() { UniqueID = 2, IsPoisonousToAnimal = false };
pool.AddItem<Animal>(dog.ID, dog);
pool.AddItem<Vegetable>(carrot.Identifier, carrot);
pool.AddItem<Mineral>(carbon.UniqueID, carbon);
Console.WriteLine("Dog is in the pool -- this statement is " + pool.ContainsKey<Animal>(dog.ID));
Console.ReadLine();
}
}
Until next time,
Happy coding