Преимущества / недостатки различных реализаций для сравнения объектов - PullRequest
18 голосов
/ 21 марта 2010

В этом вопросе участвуют 2 разные реализации одного и того же кода.

Сначала с помощью делегата создайте метод сравнения, который можно использовать в качестве параметра при сортировке коллекции объектов:

class Foo
{
    public static Comparison<Foo> BarComparison = delegate(Foo foo1, Foo foo2)
    {
        return foo1.Bar.CompareTo(foo2.Bar);
    };
}

Я использую вышеупомянутое, когда хочу иметь способ сортировки коллекции объектов Foo другим способом, чем моя функция CompareTo. Например:

List<Foo> fooList = new List<Foo>();
fooList.Sort(BarComparison);

Во-вторых, используя IComparer:

public class BarComparer : IComparer<Foo>
{
    public int Compare(Foo foo1, Foo foo2)
    {
        return foo1.Bar.CompareTo(foo2.Bar);
    }
}

Я использую вышеупомянутое, когда хочу выполнить двоичный поиск объекта Foo в коллекции объектов Foo. Например:

BarComparer comparer = new BarComparer();
List<Foo> fooList = new List<Foo>();
Foo foo = new Foo();
int index = fooList.BinarySearch(foo, comparer);

Мои вопросы:

  • Каковы преимущества и недостатки каждой из этих реализаций?
  • Какие еще способы использовать преимущества каждой из этих реализаций?
  • Есть ли способ объединить эти реализации таким образом, чтобы мне не нужно было дублировать код?
  • Можно ли выполнить как двоичный поиск, так и альтернативную сортировку коллекций, используя только одну из этих реализаций?

Ответы [ 5 ]

7 голосов
/ 21 марта 2010

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

Вы можете использовать IComparer<T> интерфейс для List<T>.Sort, что позволит вам не дублировать код.

К сожалению, BinarySearch делаетне реализовывать опцию с использованием Comparison<T>, поэтому вы не можете использовать делегат Comparison<T> для этого метода (по крайней мере, не напрямую).

Если вы действительно хотите использовать Comparison<T> для обоих, вы можете сделатьуниверсальная реализация IComparer<T>, которая приняла в своем конструкторе делегат Comparison<T> и реализовала IComparer<T>.

public class ComparisonComparer<T> : IComparer<T>
{
    private Comparison<T> method;
    public ComparisonComparer(Comparison<T> comparison)
    {
       this.method = comparison;
    }

    public int Compare(T arg1, T arg2)
    {
        return method(arg1, arg2);
    }
}
6 голосов
/ 21 марта 2010

Вероятно, самым большим преимуществом принятия Comparison<T> по сравнению с IComparer<T> является возможность писать анонимные методы. Если у меня есть, скажем, List<MyClass>, где MyClass содержит свойство ID, которое должно использоваться для сортировки, я могу написать:

myList.Sort((c1, c2) => c1.ID.CompareTo(c2.ID));

Это гораздо удобнее, чем писать целую IComparer<MyClass> реализацию.

Я не уверен, что принятие IComparer<T> действительно имеет какие-то серьезные преимущества, за исключением совместимости с устаревшим кодом (включая классы .NET Framework). Свойство Comparer<T>.Default действительно полезно только для примитивных типов; все остальное обычно требует дополнительной работы для кодирования.

Чтобы избежать дублирования кода, когда мне нужно работать с IComparer<T>, я обычно делаю одну вещь: создаю универсальный компаратор, например:

public class AnonymousComparer<T> : IComparer<T>
{
    private Comparison<T> comparison;

    public AnonymousComparer(Comparison<T> comparison)
    {
        if (comparison == null)
            throw new ArgumentNullException("comparison");
        this.comparison = comparison;
    }

    public int Compare(T x, T y)
    {
        return comparison(x, y);
    }
}

Это позволяет писать код, такой как:

myList.BinarySearch(item,
    new AnonymousComparer<MyClass>(x.ID.CompareTo(y.ID)));

Это не совсем красиво, но экономит время.

У меня есть еще один полезный класс:

public class PropertyComparer<T, TProp> : IComparer<T>
    where TProp : IComparable
{
    private Func<T, TProp> func;

    public PropertyComparer(Func<T, TProp> func)
    {
        if (func == null)
            throw new ArgumentNullException("func");
        this.func = func;
    }

    public int Compare(T x, T y)
    {
        TProp px = func(x);
        TProp py = func(y);
        return px.CompareTo(py);
    }
}

Который вы можете написать код, предназначенный для IComparer<T> как:

myList.BinarySearch(item, new PropertyComparer<MyClass, int>(c => c.ID));
1 голос
/ 21 марта 2010

Техника делегата очень короткая (лямбда-выражения могут быть еще короче), поэтому, если ваша цель - более короткий код, это является преимуществом.

Однако реализация IComparer (и его обобщенного эквивалента) делаетВаш код более тестируем: вы можете добавить некоторое модульное тестирование в ваш класс / метод сравнения.

Кроме того, вы можете повторно использовать реализацию компаратора при составлении двух или более компараторов и объединении их в качестве нового компаратора.Труднее добиться повторного использования кода с анонимными делегатами.

Итак, подведем итог:

Анонимные делегаты : более короткий (и, возможно, более чистый) код

Явная реализация : тестируемость и повторное использование кода.

0 голосов
/ 21 марта 2010

Они действительно отвечают различным потребностям:

IComparable полезно для упорядоченных объектов. Действительные числа должны быть сопоставимы, но комплексные числа не могут - это плохо определено.

IComparer позволяет определять многоразовые, хорошо инкапсулированные компараторы. Это особенно полезно, если для сравнения необходимо знать некоторую дополнительную информацию. Например, вы можете сравнить даты и время из разных часовых поясов. Это может быть сложно, и для этой цели следует использовать отдельный компаратор.

Метод сравнения сделан для простых операций сравнения, которые не являются достаточно сложными для повторного использования, чтобы иметь какое-либо значение, например. сортировка списка клиентов по имени. Это простая операция, поэтому не требует дополнительных данных. Точно так же это не присуще объекту, потому что объекты не упорядочены естественным образом.

Наконец, есть IEquatable, что может быть важно, если ваш метод Equals может решить только, равны или нет два объекта, но если нет понятия «больше» и «меньше», например комплексные числа или векторы в пространстве.

0 голосов
/ 21 марта 2010

В вашем случае преимущество наличия делегата IComparer<T> над Comparision<T> состоит в том, что вы также можете использовать его для метода Sort, поэтому вам совсем не нужна версия делегата Comparison.

Еще одна полезная вещь, которую вы можете сделать, - реализовать делегированную реализацию IComparer<T>, например:

public class DelegatedComparer<T> : IComparer<T>
{
  Func<T,T,int> _comparision;
  public DelegatedComparer(Func<T,T,int> comparision)
  {
    _comparision = comparision;
  }
  public int Compare(T a,T b) { return _comparision(a,b); }
}

list.Sort(new DelegatedComparer<Foo>((foo1,foo2)=>foo1.Bar.CompareTo(foo2.Bar));

и более продвинутая версия:

public class PropertyDelegatorComparer<TSource,TProjected> : DelegatedComparer<TSource>
{
  PropertyDelegatorComparer(Func<TSource,TProjected> projection)
    : base((a,b)=>projection(a).CompareTo(projection(b)))
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...