문제

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)

다른 팁

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top