Multi-Value IComparer

In .Net, SortedList<>’s cannot contain multiple Key values. This may be caused by the fact that the SortedList index operator [] needs to uniquely identify a list entry. However the restriction is annoying if you just want to sort some arbitrary set of data.

I came across this post Advanced IComparer // Sorting on Multiple Values on creating custom comparers implementing IComparer<T>. I especially liked Simon’s source code he posted in his comment.

However I had to fix 2 problems to make it work in my code:

First was the mishandling of ASC/DESC: when you defined the properties to be compared, you needed to add “ASC” or “DESC” after the property name. “ASC” should be the default, as in SQL SELECTs, and should not be required. I changed args.Length>0 to args.Length>1 to avoid an out-of-range error.

The second problem was more difficult to solve, as it involved the reflection API. The original code supposed all comparable values of the objects to be Properties of the respective class. I wanted the comparison also to include member variables to avoid requiring to declare { get; set; } for every variable.

So I changed the property value handling from

object o1 = x.GetType().GetProperty(prop).GetValue(x, null);

to

pi = x.GetType().GetProperty(prop);
if (pi != null)
    o1 = pi.GetValue(x, null);
else
{
    fi = x.GetType().GetField(prop);
    if (fi != null)
        o1 = fi.GetValue(x);
    else
        throw new Exception("Field or Property " + prop + " not found in " + x.GetType().ToString());
}

which checks the declared names against property names and field (member variable) names.

So here’s the code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace deviolib
{
	// 090718 http://sholliday.spaces.live.com/blog/cns!A68482B9628A842A!141.entry#comment

	public class Comparer : System.Collections.Generic.IComparer
	{

		///
		/// Sorts/Compares any type of object by any number of Properties
		///
		///
Property name followed by sort direction. e.g. 'MyProperty DESC'

		public Comparer(string property)
		{
			_properties = new string[] { property };
		}

		///
		/// Sorts/Compares any type of object by any number of Properties
		///
		///
An array of property names followed and sort direction. e.g. {'MyProperty DESC', 'Property2 ASC'}

		public Comparer(params string[] properties)
		{
			_properties = properties;
		}

		private string[] _properties;

		///
		/// An array of property names followed and sort direction. e.g. {'MyProperty DESC', 'Property2 ASC'}
		///
		public string[] Properties
		{
			get { return _properties; }
			set { _properties = value; }
		}

		public int Compare(T x, T y)
		{
			if (x.GetType() != y.GetType())
				throw new ArgumentException("Can't compare " + x.GetType() + " with " + y.GetType());

			int res = 0;
			for (int i = 0; i < _properties.Length; i++) 			{ 				string[] args = _properties[i].Split(' '); 				string prop = args[0]; 				bool desc = args.Length > 1 && args[1].Equals("DESC", StringComparison.OrdinalIgnoreCase);

				object o1 = null, o2 = null;
				PropertyInfo pi;
				FieldInfo fi;

				pi = x.GetType().GetProperty(prop);
				if (pi != null)
					o1 = pi.GetValue(x, null);
				else
				{
					fi = x.GetType().GetField(prop);
					if (fi != null)
						o1 = fi.GetValue(x);
					else
						throw new Exception("Field or Property " + prop + " not found in " + x.GetType().ToString());
				}
				//object o2 = y.GetType().GetProperty(prop).GetValue(y, null);

				pi = y.GetType().GetProperty(prop);
				if (pi != null)
					o2 = pi.GetValue(y, null);
				else
				{
					fi = y.GetType().GetField(prop);
					if (fi != null)
						o2 = fi.GetValue(y);
					else
						throw new Exception("Field or Property " + prop + " not found in " + y.GetType().ToString());
				}

				if (o1 == null)
					throw new NullReferenceException();
				if (o2 == null)
					throw new NullReferenceException();

				IComparable comp1 = o1 as IComparable;
				IComparable comp2 = o2 as IComparable;

				if (comp1 == null)
					throw new ArgumentException("Property '" + prop + "' of type " + o1.GetType() + " is not IComparable");
				if (comp2 == null)
					throw new ArgumentException("Property '" + prop + "' of type " + o2.GetType() + " is not IComparable");

				res = comp1.CompareTo(o2) * (desc ? -1 : 1);

				if (res != 0)
					return res;

			}
			return res;
		}
	}
}

enjoy πŸ˜‰

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: