Wednesday, April 29, 2009

IComparer Extension Methods for Fluent Interface

IComparerI've pretty much been a blogging fiend today! I love it when I find a topic like IComparer or Extension Methods that are entertaining enough to write about.

Today alone, I wrote articles about how IComparer works, using the proxy pattern to sort by multiple IComparers, and converting a Comparison delegate to an IComparer. Bragging aside though, I've found a fun way to combine my current favorite topics: IComparer and Extension Methods.

I wrote 3 extension methods to help me do some pretty neat things with IComparers and Comparisons. You'll see references to two of my prior articles in this post. The ComparerProxy sorts by multiple comparers and the ComparisonComparer converts a Comparison to an IComparer.

I'll show you the extension methods first, then I'll explain the reasoning behind them, and then I'll show you some usage.
public static class IComparerExtensions
{
public static IComparer<T> Then<T>(this IComparer<T> priority, IComparer<T> then)
{
return new ComparerProxy<T>(priority, then);
}

public static IComparer<T> Invert<T>(this IComparer<T> comparer)
{
return new ComparisonComparer<T>((x, y) => comparer.Compare(x, y) * -1);
}

public static IComparer<T> ToComparer<T>(this Comparison<T> comparison)
{
return new ComparisonComparer<T>(comparison);
}
}
So, lately I've been pretty big on fluent interfaces. I haven't written one yet, but I do use extension methods to make my implementations look a little more fluent. That's why, when I wrote the ComparerProxy, I thought, "Hey, wouldn't be cool if I could just take an IComparer and tack another comparer onto it? And then another? And another?" That's why I wrote the IComparer.Then() extension method.

Next, I realized that if you have a Comparison delegate, it'd be nice to be able to add that to a list of IComparers so I added the ToComparer method.

Finally, another thing that I often want to do is write an IComparer once but to be able to use it in reverse. Id est, I want to invert the logic. Obviously, I could List.Sort() and List.Reverse(), but that's much less efficient than having a CaseInsensitiveComparer and a ReverseCaseInsensitiveComparer.

The problem is, I don't really want to have to write both of those comparers. Instead, now I can call IComparer.Invert() and I get an IComparer that performs exactly like the original, but with inverted logic.

So, without further ado, here are some usage examples. I'm using the same test setup as I did with the ComparerProxy. I've also created several more IComparers to try out:
Comparison<Person> length = (x, y) => x.ToString().Length - y.ToString().Length;
IComparer<Person> lengthLast = new ComparerProxy<Person>(length.ToComparer(), last);

IComparer<Person> lastFirstComposed = last.Then(first);
I executed the following lines which produced the commented results:
_folks.Sort(length);
PrintList(_folks);

// Woody, Pete
// Woody, Lauren
// Caldwell, Cory
// Caldwell, Colin
// Brechtel, Jamus
// Woody, Royce Ann
// Brechtel, Ashley
// Caldwell, Jennifer


_folks.Sort(lengthLast);
PrintList(_folks);

// Woody, Pete
// Woody, Lauren
// Caldwell, Cory
// Brechtel, Jamus
// Caldwell, Colin
// Brechtel, Ashley
// Woody, Royce Ann
// Caldwell, Jennifer


_folks.Sort(lastFirstComposed);
PrintList(_folks);

// Brechtel, Ashley
// Brechtel, Jamus
// Caldwell, Colin
// Caldwell, Cory
// Caldwell, Jennifer
// Woody, Lauren
// Woody, Pete
// Woody, Royce Ann


_folks.Sort(last.Invert().Then(first));
PrintList(_folks);

// Woody, Lauren
// Woody, Pete
// Woody, Royce Ann
// Caldwell, Colin
// Caldwell, Cory
// Caldwell, Jennifer
// Brechtel, Ashley
// Brechtel, Jamus

No comments:

Post a Comment