Creating a Strongly Typed Heterogeneous Collection in .NET 2.0.
I found a solution to an interesting problem while developing an application for a state licensing board. I was using generics for strongly-typed collections of objects, but had a need to have collections of several types of objects that are historical events for a person. In this case, as a person goes through the process of becoming licensed, applications are required for exams, different exams must be taken and passed, and other requirements must be met before licensing can be completed. The historical events don’t end there though. Continuing education requirements must be maintained, complaints might be filed against the licensee, the license may become inactive and later reinstated, or a number of other events could occur. I wanted to be able to maintain a history for each person from first application through licensing and beyond. My first thought was to just maintain collections for each type of historical event. It soon became obvious I would have many collections to manage, so another solution seemed necessary.
After thinking about several approaches, I decided to make a base class named HistoryItem and all of the historical events now derive from it. If you are thinking these items are not related you are partially correct. A license is not a complaint and a complaint is not a license, so there isn’t an “is-a” relationship. A license doesn’t have a complaint and a complaint doesn’t have a license, so there isn’t a “has-a” relationship either. The same can be said about the other historical items and events. Normally we would say the items are not at all related. But, they are historical items or events for a particular person. In other words, they are related because we want to treat them the same way, as historical items. With this approach all the history can be stored in a single collection which will be much easier to manage.
Feeling proud of myself for solving this problem, I turned my attention to manipulating the collections and ran into the reason we usually don’t store different kinds of things in the same collection. How could I compare two items in the collection when they could be different types? I could see the algorithm starting to take shape. If the first item is this type and the second item is that type do something, but if the first item is that type and the second is this type, then do something else, but if they are both the same type then compare some properties to see if they are equal or not. Well, the algorithm is as ugly as that description. Another solution seemed necessary.
I decided comparers would be a big help, so I developed a set of classes that implemented the IComparer interface. If you aren’t familiar with that interface, it has one method named Compare. Compare takes two arguments and since I was using generic collections I wanted strongly typed comparers. Both arguments are HistoryItems. Now I can compare items in my collection, and search for a particular item I might find interesting. But wait, how do I know which comparer to use? I could see the algorithm taking shape. If I’m interested in this type use this comparer, but if I’m interested in that type use that comparer. Once again I was faced with an ugly algorithm and another solution seemed necessary.
What I really wanted was some mechanism that could give me the comparer I needed to use. Fortunately, that is what a factory does. But, a factory needs some information so it can make a decision about what kind of thing to create. I decided to hand my ComparerFactory the HistoryItem currently of interest and ask the factory to provide a comparer I can use with that item. I could see the factory algorithm starting to take shape. If I get this type then create this comparer, but if I get that type then create that comparer, or if I get the other type then create the other comparer. That sounded like another ugly algorithm. It could get even uglier if several new types of items and comparers were developed, so another solution seemed necessary.
If my factory could create any type without knowing anything about the type it was creating all my problems would be solved. I decided my solution should not depend on the type of the item I need to compare, but my item should be able to tell the factory what type of comparer to create. I decided to create a custom attribute that could decorate my items and the factory could use reflection to learn what type of comparer to create. Here is my attribute and how to apply it to a class.
[AttributeUsage(AttributeTargets.Class,
AllowMultiple = false, Inherited = false)]
public class CompatibleComparerAttribute : System.Attribute
{
private string _comparerName;
public string ComparerName
{
get { return _comparerName; }
}
public CompatibleComparerAttribute(string comparerName)
{
_comparerName = comparerName;
}
}
Now apply it to a class.
[CompatibleComparer(“ApplicationComparer”)]
public class Application : HistoryItem
{
. . .
}
Now, my factory knows what type of comparer to create. I can see the algorithm starting to take shape. If I need to create a “ApplicationComparer” then create an instance of ApplicationComparer, but if I need to create a “ThisComparer”, then create an instance of ThisComparer, or if I need to create a “ThatComparer” then create an instance of ThatComparer. My algorithm is as ugly as ever and another solution seems necessary.
The Solution – Putting It All Together
The System.Runtime.Remoting namespace contains a nifty little class named Activator. If you haven’t used it before check it out, it’s very useful. It can create an instance of a class if it knows its full namespace and class name. So, I just need to pass the Activator “FullNameSpace.ClassName” and it will create an instance for me. Putting it all together, I am using a strongly-typed generic collection so I can treat all my HistoryItem instances the same. Each class derived from HistoryItem is decorated with my custom attribute that contains the full namespace and class name for the comparer to use. My factory can use reflection to discover what to create with the Activator. Now, my code looks similar to this:
History history = new History();
. . .
HistoryItem item = new License();
History.Add(item);
. . .
IComparer<HistoryItem> comparer = ComparerFactory.CreateComparer(item);
int index = history.IndexOf(item, comparer);
The factory code itself is very short; this is the only method.
public static IComparer<HistoryItem> CreateComparer(HistoryItem item)
{
object[] attrs = item.GetType().GetCustomAttributes(
typeof(CompatibleComparerAttribute), false);
if (attrs.Length == 0)
{
return null;
}
CompatibleComparerAttribute attr =
attrs[0] as CompatibleComparerAttribute;
ObjectHandle objHandle =
Activator.CreateInstance(null, attr.ComparerName);
return objHandle.Unwrap() as IComparer<HistoryItem>;
}
Now my code is not ugly, and can handle new HistoryItems and comparers in the future. I’m happy and life is good. The .NET library came through with a solution that is simple and can be easily maintained.

0 Comments:
Post a Comment
<< Home