Czy interfejs IComparable jest przestarzały / „szkodliwy”?

11

IComparable działa tylko w jedną stronę

Powiedzmy, że masz Employeeklasę. W jednym widoku chcesz pokazać wszystkie Employeesposortowane według nazwy - w innym według adresu. Jak zamierzasz to osiągnąć? Nie z IComparable, przynajmniej nie w idiomatyczny sposób.

IComparable ma logikę w niewłaściwym miejscu

Interfejs jest używany przez wywołanie .Sort(). W widoku pokazującym Customerposortowane według nazwy, w ogóle nie ma kodu, który wskazywałby , jak będzie sortowany.
Z drugiej strony Customerklasa zakłada, w jaki sposób będzie używana - w tym przypadku będzie używana na liście posortowanej według nazw.

IComparable jest używany niejawnie

W porównaniu z alternatywami bardzo trudno jest zobaczyć, gdzie stosowana jest logika porównawcza - lub w ogóle. Zakładając standardowe IDE i zaczynając od Customerklasy, będę musiał

  1. Wyszukaj wszystkie odniesienia do Customer
  2. Znajdź odniesienia, które są używane na liście
  3. Sprawdź, czy te listy kiedykolwiek .Sort()do nich dzwoniły

Co gorsza, jeśli usuniesz IComparableimplementację, która jest nadal używana, nie pojawi się błąd ani ostrzeżenie. Jedyne, co dostaniesz, to złe zachowanie we wszystkich miejscach, które były zbyt niejasne, abyś o tym pomyślał.

Te problemy łącznie, a także zmieniające się wymagania

Powodem, dla którego pomyślałem o tym, jest to, że poszło mi nie tak. Z radością korzystam IComparablez aplikacji od 2 lat. Teraz wymagania się zmieniły i rzecz musi zostać posortowana na 2 różne sposoby. Zauważyłem, że nie jest fajnie przechodzić przez kroki opisane w poprzedniej sekcji.

Pytanie

Te problemy sprawiają, że uważam się IComparableza gorszy IComparerlub .OrderBy()do tego stopnia, że ​​nie widzę żadnego ważnego przypadku użycia, który nie byłby lepiej obsługiwany przez alternatywy.
Czy zawsze lepiej jest używać IComparerlub LINQ, czy też są zalety / przypadki użycia, których tu nie widzę?

R. Schmitz
źródło
2
Twoim nowym wymogiem „sortowania na dwa różne sposoby” jest czerwony śledź. Aby go rozwiązać, wystarczy przekazać inny komparator do swojej funkcji sortowania.
Robert Harvey,
@RobertHarvey W takim razie nie będziesz IComparablejuż używać , co wzmacnia mój punkt widzenia.
R. Schmitz,
Nie zapominaj, że jeśli korzystasz z SortedXXXkolekcji, wymagają one, aby przechowywane elementy były IComparablelub miały IComparerzapewnioną. Zauważ też, że odwrócenie naturalnej kolejności sortowania za pomocą jednego urządzenia porównującego i sprawienie, by działała ze wszystkimi IComparableobiektami , jest banalne .
Berin Loritsch,
2
Nie ma znaczenia, że ​​istnieją dwa różne interfejsy. IComparablejest uważany za domyślny mechanizm porównania. IComparerjest używany, gdy chcesz zastąpić domyślny mechanizm porównywania.
Robert Harvey,
Przykład ReverseComparer<T>: gist.github.com/jackfarrington/078e7af7bc82482aa634
Berin Loritsch

Odpowiedzi:

14

IComparablema wspomniane ograniczenia, które są poprawne. Jest to interfejs, który był już dostępny w .NET Framework 1.0, w którym te funkcjonalne alternatywy i Linq nie były dostępne. Tak, można by uznać, że jest to przestarzały element frameworka, który jest głównie przechowywany w celu zachowania kompatybilności wstecznej.

Jednak w przypadku wielu prostych struktur danych jeden sposób sortowania jest prawdopodobnie wystarczający lub naturalny. W takich przypadkach posiadanie jednego kanonicznego miejsca do implementacji relacji zamówienia jest nadal dobrym sposobem na zachowanie kodu DRY, zamiast zawsze powtarzania tej samej logiki przy każdym wywołaniu do OrderBycałego miejsca.

Jak pisałeś, „z radością używasz IComparable w swojej aplikacji od 2 lat”, więc wydaje mi się, że dobrze ci to służyło przez długi czas. Kiedy teraz musisz zweryfikować, zmienić i przetestować wszystkie połączenia z Sort, może to być również znak, że robiłeś ten sam rodzaj logiki sortowania w wielu miejscach, co nie jest winą IComparable. Może to być okazja do scentralizowania większej ilości tej logiki w jednym miejscu, dzięki czemu Twój kod będzie bardziej SUCHY.

Doktor Brown
źródło
Dobra uwaga na temat prostych struktur danych. Jednak ostatni akapit nie ma dla mnie 100% sensu. Gdybym tego nie użył IComparable, cały wcześniej istniejący kod sortowania pozostałby nietknięty w ich odpowiednich widokach, a ja dodałbym tylko nowy kod sortowania dla nowego widoku.
R. Schmitz,
@ R.Schmitz Czy istniejący rodzaj działałby poprawnie bez IComparableimplementacji, którą napisałeś?
Robert Harvey
3
@ R. Schmitz: Jasne, ale teraz zobowiązujesz się zawsze zapewniać komparator (chyba że używasz OrderBy, oczywiście). Dzięki IComparabledostajesz domyślną implementację za darmo, a czasem nawet nie musisz jej pisać.
Robert Harvey,
2
@ R. Schmitz: Twój ostatni komentarz ładnie podsumowuje ten punkt. Poszedłbym jednak trochę dalej. Załóżmy, że masz typ liczbowy podobny do BigInteger. Jeśli nie zaimplementowałoby operatorów porównania / interfejsów, jak byś sam wdrożył IComparer ? Potrzebny byłby dostęp do wewnętrznych struktur danych, aby zrobić to efektywnie lub wcale. Załóżmy, że masz typ podobny do klienta; obiekty publiczne, które chcesz posortować , mają porównywarki. Dla mnie to jest różnica: implementuj, IComparable<T>jeśli nierozsądne byłoby oczekiwanie od osoby dzwoniącej implementacji modułu porównującego.
Eric Lippert,
1
If I hadn't used IComparable, all the pre-existing sorting code would have been left untouched in their respective views, while I'd only add new sorting code for the new view.Tylko dlatego, że IComparablebyło lepsze rozwiązanie w tym czasie , nie oznacza, że jest to najlepsze rozwiązanie dzisiaj . Twój pierwszy komentarz tutaj sugeruje, że „jest IComparable lub nic”, co nie jest prawdą, problem mógł zostać rozwiązany na wiele różnych sposobów. Aplikacje mogą rosnąć w rozmiarze / skali, a rzeczy, które kiedyś wyglądały odpowiednio, mogą nie być w stanie sprostać rosnącym wymaganiom aplikacji.
Flater
1

Zgadzam się z twoimi sentymentami IComparable

Spójrz tylko na uwagi Array.Sort()

  • Każdy element tablicy musi implementować IComparableinterfejs, aby mógł być porównywany z każdym innym elementem tablicy. (lub zgłoszony wyjątek)
  • Jeśli sortowanie nie zakończy się pomyślnie, wyniki są niezdefiniowane.

Prawdopodobnie nigdy nie będziemy mieć motywacji, jednak! rozważ object.Equals()metodę na każdym obiekcie, która pozwala porównywać obiekty ze sobą, aby sprawdzić, czy są one „takie same”

Masz już to, ale powierzono Array.Sort()ci zadanie dodania, które możesz chcieć dodaćobject.Compare(object)

Ewan
źródło