Есть ли разумный сценарий для сохраняющего состояние IComparer <T>? - PullRequest
3 голосов
/ 22 июля 2009

Я никогда не писал с сохранением IComparer<T> с конструктором по умолчанию. Все стандартные реализации библиотеки, которые я проверил в Reflector, также не имеют состояния. Поэтому я хотел бы предположить, что я могу свободно кэшировать IComparer<T> следующим образом:

PriorityQueue<TPriority, TComparer> where TComparer : IComparer<TPriority>, new()
{
    private static TComparer _comparer = new TComparer();

    public PriorityQueue() {...}
    ...
}

вместо

PriorityQueue<TPriority>
{
    private IComparer<TPriority> _comparer;

    public PriorityQueue(IComparer<TPriority> comparer) { 
        _comparer = comparer;
        ...
    }

    ...
}

Итак, вот вопрос: вы когда-нибудь писали / видели IComparer<T>, для которого это сломалось бы? Если да, то как часто это происходит?

РЕДАКТИРОВАТЬ: причина, по которой я действительно не хочу накладных расходов второй версии в этом случае, состоит в том, что структура данных является постоянной Он реализован в виде дерева, где узлы не имеют родительской / корневой ссылки. Таким образом, это будет не одна ссылка для сравнения на очередь, а одна ссылка для сравнения на узел! Мой оригинальный дизайн был просто использовать IComparable<T> и рекомендовать написать структуру-обертку для пользовательских сравнений.

Ответы [ 3 ]

3 голосов
/ 22 июля 2009

Что ж, наличие статического компаратора означает, что вы не можете иметь разные сравнения в разных очередях; это может быть проблемой ... иногда люди делают нуждаются в пользовательском сравнении; например, если они не контролируют тип. Мой подход по умолчанию будет:

PriorityQueue<TPriority>
{
    private IComparer<TPriority> _comparer;

    public PriorityQueue(IComparer<TPriority> comparer) { 
        _comparer = comparer;
        ...
    }

    public PriorityQueue() : this(Comparer<T>.Default) {}
}

Re Stateful сравнения; да, я написал несколько - особенно для написания проекционных компараторов в стиле LINQ ... например, что-то вроде:

public static class ProjectionComparer<TSource>
{
    public static IComparer<TSource> CompareBy<TValue>(
        Func<TSource, TValue> selector)
    {
        return CompareBy<TValue>(selector, Comparer<TValue>.Default);
    }
    public static IComparer<TSource> CompareBy<TValue>(
        Func<TSource, TValue> selector,
        IComparer<TValue> comparer)
    {
        return new ProjectionComparerItem<TValue>(
            selector, Comparer<TValue>.Default);
    }
    class ProjectionComparerItem<TValue> : IComparer<TSource>
    {
        private readonly IComparer<TValue> comparer;
        private readonly Func<TSource, TValue> selector;
        public ProjectionComparerItem(
            Func<TSource, TValue> selector,
            IComparer<TValue> comparer)
        {
            this.selector = selector;
            this.comparer = comparer;
        }
        public int Compare(TSource x, TSource y)
        {
            // TODO: some null stuff...
            return comparer.Compare(selector(x), selector(y));
        }
    }
}

, что позволяет:

IComparer<Customer> comparer = ProjectionComparer<Customer>
          .CompareBy(cust => cust.Name);

Экземпляр "сортировка по имени" сравнения.

1 голос
/ 22 июля 2009

Да, но я думаю, что это довольно необычно.

В редких случаях вы хотите выполнить сравнение, зависящее от других данных. Например, у нас есть некоторые пространственные процедуры, в которых мы подаем ось, которая используется для сравнения, как часть IComparer.

При этом довольно легко обойти это, просто используя отдельный класс сравнения, и это, вероятно, лучший дизайн во многих отношениях. Вы налагаете ограничение на реализацию IComparer<T>, которая действительно должна существовать, поэтому я бы задокументировал ваше обоснование.

Мое личное предпочтение было бы сделать IComparer<T> нестатичным и предоставить два конструктора - один, который принимает внешний экземпляр, и другой, который создает компаратор по умолчанию. У вас есть дополнительные издержки компаратора на очередь, но это довольно минимально (почти 0, если у вас нет состояния, так как это только одна ссылка на объект на «пустой» объект).

0 голосов
/ 31 июля 2009

Другой возможный подход:

private class PriorityQueueImpl<TPriority, TComparer> where TComparer : IComparer<TPriority> {
    // all methods accept a TComparer
    // generic in TComparer to avoid boxing for struct TComparers and permit inlining for sealed TComparers
}

public struct PriorityQueue<TPriority, TComparer> where TComparer : IComparer<TPriority> {
    private readonly PriorityQueueImpl<TPriority, TComparer> _impl;
    private readonly TComparer _comparer;

    // methods delegate to _impl
}

Я могу пойти с этим, по крайней мере, в некоторых случаях.

...