2010-03-21 12 views
17

To pytanie dotyczy 2 różnych implementacji zasadniczo tego samego kodu.Zalety/Wady różnych implementacji porównywania obiektów

pierwsze, używając delegata stworzyć metodę porównania, które mogą być używane jako parametr przy sortowaniu kolekcji obiektów:

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

używam powyżej, gdy chcę mieć sposób sortowania kolekcję Obiekty Foo w inny sposób niż oferuje moja funkcja CompareTo. Na przykład:

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

drugie, używanie IComparer:

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

używam powyższe gdy chcę zrobić binarną poszukiwania obiektu Foo w zbiorze obiektów Foo. Na przykład:

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

moje pytania są następujące:

  • Jakie są wady i zalety każdego z tych wdrożeń?
  • Jakie są inne sposoby wykorzystania każdej z tych implementacji?
  • Czy istnieje sposób połączenia tych implementacji w taki sposób, że nie muszę duplikować kodu?
  • Czy mogę uzyskać zarówno wyszukiwanie binarne, jak i alternatywny sortowanie kolekcji przy użyciu tylko jednej z tych implementacji?

Odpowiedz

5

Prawdopodobnie największą zaletą przyjęcia Comparison<T> w przeciwieństwie do IComparer<T> jest możliwość pisania metod anonimowych. Jeśli mam, powiedzmy, o List<MyClass>, gdzie MyClass zawiera właściwość ID, które powinny być używane do sortowania, mogę napisać:

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

co jest dużo wygodniejsze niż konieczności napisać całą IComparer<MyClass> realizację.

Nie jestem pewien, czy akceptacja IComparer<T> ma naprawdę duże zalety, z wyjątkiem zgodności ze starszym kodem (w tym klasami .NET Framework).Właściwość Comparer<T>.Default jest użyteczna tylko dla typów pierwotnych; wszystko inne zwykle wymaga dodatkowej pracy, aby się kodować.

Aby uniknąć powielania kodu kiedy muszę pracować z IComparer<T>, jedno zwykle zrobić, to stworzyć ogólny porównywarka coś takiego:

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); 
    } 
} 

Pozwala to pisanie kodu, takich jak:

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

To nie jest całkiem ładne, ale oszczędza trochę czasu.

Inną użyteczną klasę mam tylko to jedno:

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); 
    } 
} 

którym można napisać kod przeznaczony dla IComparer<T> jak:

myList.BinarySearch(item, new PropertyComparer<MyClass, int>(c => c.ID)); 
+0

Świetne przykłady kodu! –

7

Naprawdę nie ma żadnej korzyści pod względem wydajności. To naprawdę kwestia wygody i łatwości obsługi kodu. Wybierz preferowaną opcję. Biorąc to pod uwagę, omawiane metody ograniczają nieznacznie twoje wybory.

Możesz użyć interfejsu IComparer<T> dla List<T>.Sort, który pozwoliłby ci nie powielać kodu.

Niestety, BinarySearch nie implementuje opcji przy użyciu Comparison<T>, więc nie można używać delegata Comparison<T> dla tej metody (przynajmniej nie bezpośrednio).

Jeśli naprawdę chciał wykorzystać Comparison<T> dla obu, można zrobić rodzajowe IComparer<T> realizację że wziął Comparison<T> delegata w jej konstruktora, a realizowany 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

W Twoim przypadku zaletą posiadania IComparer<T> nad Comparision<T> delegata, jest to, że można go używać również do sposobu sortowania, więc nie trzeba wersję Comparison delegata w ogóle.

Inną użyteczną rzeczą, jaką można zrobić, to wdrożenie delegowanego IComparer<T> realizację takiego:

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)); 

i bardziej zaawansowaną wersję:

public class PropertyDelegatorComparer<TSource,TProjected> : DelegatedComparer<TSource> 
{ 
    PropertyDelegatorComparer(Func<TSource,TProjected> projection) 
    : base((a,b)=>projection(a).CompareTo(projection(b))) 
} 
+0

Typo: brakuje zamykającego nawiasu '}' na linii nr 8 pierwszy fragment kodu. –

1

Technika delegat jest bardzo krótki (wyrażenia lambda może być nawet krótszy), więc jeśli twoim celem jest krótszy kod, to jest to zaleta.

Jednak implementacja programu IComparer (i jego odpowiednika generycznego) sprawia, że ​​twój kod jest bardziej testowalny: możesz dodać testowanie jednostek do swojej klasy/metody porównania.

Co więcej, można ponownie wykorzystać implementację porównywalnika podczas komponowania dwóch lub więcej porównywarek i łączenia ich jako nowego porównywalnika. Ponowne użycie kodu z anonimowymi delegatami jest trudniejsze do osiągnięcia.

Tak więc, aby podsumować:

anonimowych Delegaci: krótsza (a może czystsze) Kod

Explicit Realizacja: testowalność i ponowne wykorzystanie kodu.

+1

Zgadzam się z punktem ponownego użycia kodu, jednak nie jestem przekonany co do testowalności. Dlaczego metoda akceptująca 'IComparer ' może być łatwiejsza do przetestowania niż akceptująca 'Porównanie '? Obaj stosują odwrócenie kontroli. – Aaronaught

+1

@Aarona, myślę, że jestem źle zrozumiany: ** obydwie ** jawne implementacje są łatwe do przetestowania ('IComparer ' i 'Porównanie '), w przeciwieństwie do anonimowych delegatów, które są trudniejsze do przetestowania. –

+0

Ach, rozumiem, masz na myśli testowanie jednostkowe samego "IComparer ", a nie metodę, która je akceptuje. Nie mogę sobie wyobrazić, że chciałbym przetestować jedną z nich, ale masz rację, zdecydowanie łatwiej jest pisać testy przeciwko, jeśli chcesz. – Aaronaught

0

Oni naprawdę zaspokoić różne potrzeby:

IComparable jest przydatna dla obiektów, które zostały zamówione. Liczby rzeczywiste powinny być porównywalne, ale liczby złożone nie mogą - jest źle zdefiniowane.

IComparer pozwala zdefiniować wielokrotnego użytku, dobrze obudowane porównywarki. Jest to szczególnie przydatne, gdy porównanie musi znać dodatkowe informacje. Możesz na przykład porównać daty i godziny z różnych stref czasowych. Może to być skomplikowane, aw tym celu należy zastosować osobny porównywalnik.

Metoda porównania dotyczy prostych operacji porównania, które nie są wystarczająco skomplikowane, aby można je było ponownie wykorzystać, np. sortowanie listy klientów według ich imienia. Jest to prosta operacja, dlatego nie wymaga dodatkowych danych. Podobnie nie jest to nieodłączne dla obiektu, ponieważ obiekty nie są naturalnie uporządkowane w żaden sposób.

Wreszcie istnieje IEquatable, co może być ważne, jeśli metoda Equals może jedynie zdecydować, czy dwa obiekty są równe, czy nie, ale jeśli nie ma pojęcie „większe” i „mniejsze”, na przykład liczby zespolone lub wektory w przestrzeni.

Powiązane problemy