<= Home
IComparable, Custom Objects, and good OOP...oh my
Anyone that has worked with me knows that I am anti-datasets and pro-creating custom business object/entities. Microsoft has spent some time making their controls, like the DataGrid, work very cohesively with the System.Data classes (primarily DataTables and DataSets). To take advantage of the same functionality with my custom collections I have to implement the IBindingList. The IBindingList is the interface that is at the root of the DataTable and is what the DataGrid and other controls use to effectively bind data to the UI.
Sorting
Along with having a collection of my custom objects, I need a way to sort the list. I had created a class called ObjectComparer and it implements the interface IComparer. This is the interface the sort method on the ArrayList accepts as an argument. I created this along time ago but recently a co-worker brought to my attention another fellow
He did a decent job except that in his example the comparer only compares on properties of objects that are of type int, double, string, and DateTime and has a method to handle each specific type. The advantage of using the comparer I provide below is that it doesn’t care or need to know about the type of the property on the object you are comparing against, as long as the type implements ICompareable interface (The IComparer counterpart). Many types in the .Net framework already implement this interface (including the int, double, string, and DateTime). Plus if you create a custom class, you can also extend your class with it’s own implementation of how to compare itself against another object of the same type by implementing the IComparable interface. This really demonstrates the power of interfaces because my ObjectComparer code never needs to change to handle any specific types because it only deals with the IComparable interface. Where as with the other developers sample, everytime you wish to be able to compare on another type, you have to edit his object comparer. Below is my implementation of a ObjectComparer.
public class ObjectComparer : System.Collections.IComparer
{
private const int COMPARE_EQUAL = 0;
private const int COMPARE_GREATERTHAN = 1;
private const int COMPARE_LESSTHAN = -1;
private string _propertyNames = string.Empty;
//params string[] names
public ObjectComparer(string propertyName){
this._propertyNames = propertyName;
}
public int Compare(object x, object y){
object a = x.GetType().GetProperty(this._propertyNames).GetValue(x, null);
object b = y.GetType().GetProperty(this._propertyNames).GetValue(y, null);
bool aIsNotNothing = a != null;
bool bIsNotNothing = b != null;
bool notSameType = a.GetType() == b.GetType();
if(aIsNotNothing && !bIsNotNothing){
return COMPARE_GREATERTHAN;
}
else if(!aIsNotNothing && bIsNotNothing){
return COMPARE_LESSTHAN;
}
else if(!aIsNotNothing && !bIsNotNothing){
return COMPARE_EQUAL;
}
else if(notSameType){
return COMPARE_LESSTHAN;
}
if (a is System.IComparable){
return (a as System.IComparable).CompareTo(b);
}
else{
throw new InvalidOperationException(a.GetType().Name + " is not supported by the ObjectComparer.");
}
}
}
MultiKey Comparison Sorting
I first saw the other developer’s custom object IComparer last week when he posted on how he changed his original implementation to now handle multiple properties to do the comparisons on to sort. My co-worker suggested that we may want to implement the same functionality. When I saw what the author had changed the inner workings of his class by modifying it, alarms went off. This is a big violation of the open-closed object oriented principle. Basically the open-closed principle means the “Objects should be open for extension and closed to modification.” By extension, the principle means either by preferably using inheritance or composition. Modification means going into “already working” classes and changing implementation – potentially making your class unstable. So instead of modifying my ObjectComparer I decided to create a new class called the MultiKeyObjectComparer which internally (composition) uses several instances of the ObjectComparer and its already stable working functionality. In the defense of the original author, some may argue that his API (public interface) would be friendlier in the sense that you could continue to use his object comparer and another developer would see the new functionality using intellisense because of the additional constructors and methods, where as with my design the developer would have to be aware of the new MultiKeyObjectComparer that I created to benefit from it.
public class MultiKeyObjectComparer : System.Collections.IComparer
{
private ObjectComparer[] _comparers;
public MultiKeyObjectComparer(params string[] propertyNames){
this._comparers = this.CreateComparers(propertyNames);
}
private ObjectComparer[] CreateComparers(string[] propertyNames){
System.Collections.ArrayList list = new System.Collections.ArrayList();
foreach(string propertyName in propertyNames){
list.Add(new ObjectComparer(propertyName));
}
return (list.ToArray(typeof(ObjectComparer)) as ObjectComparer[]);
}
public int Compare(object x, object y){
int comparison = 0;
foreach(ObjectComparer comparer in this._comparers){
comparison = comparer.Compare(x, y);
if(comparison != 0)
{
return comparison;
}
}
return comparison;
}
}
(On a side point, after implementing this, the next morning another altogether separate co-worker requested the functionality for a UI he was doing, completely unaware that I had already been looking into this…weird coincidence).