2010-03-21 3 views
17

Эти вопросы включают в себя две различные реализации по существу одного и того же кода.Преимущества/Недостатки различных реализаций для сравнения объектов

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

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

Возможно, самым большим преимуществом для принятия 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)); 
+0

Отличные примеры кода! –

7

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

Вы можете использовать интерфейс 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); 
    } 
} 
0

В вашем случае преимущество имея IComparer<T> над Comparision<T> делегата, является то, что вы можете также использовать его для метода сортировки, так что вам не нужна версия 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))) 
} 
+0

опечатка: отсутствует закрывающая скобка '}' в строке 8 первый фрагмент кода. –

1

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

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

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

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

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

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

+1

Я согласен с точкой повторного использования кода, однако я не очень убежден в возможности тестирования. Почему метод, допускающий «IComparer », будет легче тестировать, чем принимать «Сравнение »? Они оба используют инверсию контроля. – Aaronaught

+1

@ Я думаю, что меня неправильно поняли: ** и ** явные реализации легко проверить ('IComparer ' и 'Сравнение '), в отличие от анонимных делегатов, которые сложнее проверить. –

+0

Ах, я понимаю, вы имеете в виду unit-тестирование самого IComparer '', а не метода, который его принимает. Не могу себе представить, что на самом деле вы хотите провести тестирование одного из них, но вы правы, это определенно легче написать тесты против, если вы этого хотите. – Aaronaught

0

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

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

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

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

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

Смежные вопросы