I have a class (Patch) that I want to have sorted so I implemented IComparer.
However, it needs to be sorted depending on how the user wants it, e.g.: - key1, key2, key3 - key1, key3, key2
For each key compare I have written a IComparer class, however, I was wondering how to implement its connection. i.e. when sorting I only can pass one IComparer instance.
Or should I make an IComparer class for each kind of full sorting, i.e. IComparerKey1Key2Key3, IComparerKey1Key3Key2 etc?
You could make a generic comparer that takes a delegate to select the key:
class ByKeyComparer<T, TKey> : IComparer<T>
{
private readonly Func<T, TKey> _keySelector;
private readonly IComparer<TKey> _keyComparer;
public ByKeyComparer(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
if (keySelector == null) throw new ArgumentNullException("keySelector");
_keySelector = keySelector;
_keyComparer = keyComparer ?? Comparer<TKey>.Default;
}
public int Compare(T x, T y)
{
return _keyComparer.Compare(_keySelector(x), _keySelector(y));
}
}
With a helper class to take advantage of type inference (so you don't need to specify the type of the key):
static class ByKeyComparer<T>
{
public static IComparer<T> Create<TKey>(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
return new ByKeyComparer<T, TKey>(keySelector, keyComparer);
}
}
You can use it like this:
var patchVersionComparer = ByKeyComparer<Patch>.Create(p => p.Version);
patches.Sort(patchVersionComparer);
If you need to combine several compare keys, you can create a comparer that uses other comparers:
class CompositeComparer<T> : IComparer<T>
{
private readonly IEnumerable<IComparer<T>> _comparers;
public CompositeComparer(IEnumerable<IComparer<T>> comparers)
{
if (comparers == null) throw new ArgumentNullException("comparers");
_comparers = comparers;
}
public CompositeComparer(params IComparer<T>[] comparers)
: this((IEnumerable<IComparer<T>>)comparers)
{
}
public int Compare(T x, T y)
{
foreach (var comparer in _comparers)
{
int result = comparer.Compare(x, y);
if (result != 0)
return result;
}
return 0;
}
}
Example usage:
var comparer = new CompositeComparer<Patch>(
ByKeyComparer<Patch>.Create(p => p.Key1),
ByKeyComparer<Patch>.Create(p => p.Key2),
ByKeyComparer<Patch>.Create(p => p.Key3));
patches.Sort(comparer);
EDIT: here's a more fluent API:
static class ByKeyComparer<T>
{
public static IComparer<T> CompareBy<TKey>(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
return new ByKeyComparer<T, TKey>(keySelector, keyComparer);
}
}
static class ComparerExtensions
{
public static IComparer<T> ThenBy<T, TKey>(this IComparer<T> comparer, Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
var newComparer = ByKeyComparer<T>.CompareBy(keySelector, keyComparer);
var composite = comparer as CompositeComparer<T>;
if (composite != null)
return new CompositeComparer<T>(composite.Comparers.Concat(new[] { newComparer }));
return new CompositeComparer<T>(comparer, newComparer);
}
}
Example:
var comparer = ByKeyComparer<Patch>.CompareBy(p => p.Key1)
.ThenBy(p => p.Key2)
.ThenBy(p => p.Key3);
patches.Sort(comparer);
(obviously you might want to add *Descending
versions of the CompareBy
and ThenBy
methods to allow ordering in descending order)