Domanda

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?

È stato utile?

Soluzione

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)

Altri suggerimenti

If you can use LINQ it'll be quite easy to sort classes like this. Consider you have a List of Patch List<Patch> and you wanna sort it by key2, key1 and key4. What you do is:

List<Patch> patches = new List<Patch>();
patches = GetPatches().ToList().OrderBy(p=>p.Key2).ThenBy(p=>p.Key1).ThenBy(p=>p.Key4).ToList();

That's all. we love linq. :)

First ToList is not needed if function returns list itself.

You also can use LINQ Dynamic Query Library or have a look at Dynamic LINQ OrderBy

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top