2009-05-27 9 views
9

Kiedy chcę ograniczenie typ T, aby być porównywalne, należy użyć:Interfejs ograniczenie dla IComparable

where T : IComparable 

lub

where T : IComparable<T> 

nie mogę uzyskać moja głowa wokół jeśli # 2 marki sens. Ktoś może wyjaśnić, jaka byłaby różnica?

Odpowiedz

2

Model IComparable<T> umożliwia porównanie kompilatora o silnym typie.

Można mieć

public int CompareTo(MyType other) 
{ 
    // logic 
} 

jak przeciwstawić

public int CompareTo(object other) 
{ 
    if (other is MyType) 
     // logic 
} 

Weźmy na przykład następny przykład czarownica realizuje oba interfejsy:

public class MyType : IComparable<MyType>, IComparable 
{ 
    public MyType(string name, int id) 
    { Name = name; Id = id; } 

    public string Name { get; set; } 
    public int Id { get; set; } 

    public int CompareTo(MyType other) 
    { 
     if (null == other) 
      throw new ArgumentNullException("other"); 
     return (Id - other.Id > 0 ? 1 : 0); 
    } 

    public int CompareTo(object other) 
    { 
     if (null == other) 
      throw new ArgumentNullException("other"); 
     if (other is MyType) 
      return (Id - (other as MyType).Id > 0 ? 1 : 0); 
     else 
      throw new InvalidOperationException("Bad type"); 
    } 
} 


MyType t1 = new MyType("a", 1); 
MyType t2 = new MyType("b", 2); 
object someObj = new object(); 

// calls the strongly typed method: CompareTo(MyType other) 
t1.CompareTo(t2); 
// calls the *weakly* typed method: CompareTo(object other) 
t1.CompareTo(someObj); 

Jeśli MyType został zrealizowany tylko z IComparable<MyType>, drugi compareTo(someObj) jest czasem kompilacji błąd. Jest to jedna z zalet silnie wpisanych generyków.

Z drugiej strony istnieją metody w ramach, które wymagają nietypowych IComparable, takich jak Array.Sort. W takich przypadkach warto rozważyć wdrożenie obu interfejsów, jak w tym przykładzie.

+1

Bruno: w twoim kodzie, wolałbym delegować zadanie porównania w CompareTo (obiekt) do CompareTo (MyType) w drugiej gałęzi CompareTo (obiekt), zamiast powielać kod. – Steve

5

Główną różnicą pomiędzy IComparable i IComparable <> jest to, że najpierw jest wstępnie rodzajowych więc pozwala wywołać porównanie metody z dowolnego obiektu, natomiast drugi wymusza że podziela tego samego typu:

IComparable - CompareTo(object other); 
IComparable<T> - CompareTo(T other); 

Wybrałbym drugą opcję pod warunkiem, że nie zamierzasz używać żadnej starej biblioteki .net 1.0, gdzie typy nie mogą implementować nowoczesnego, ogólnego rozwiązania. Zyskasz wzrost wydajności, ponieważ unikniesz boksowania, a porównania nie będą musiały sprawdzać zgodności typów, a także uzyskasz ciepłe uczucie, które wynika z robienia rzeczy w najbardziej nowatorski sposób ...


Aby odpowiedzieć na bardzo dobry i trafny punkt Jeffa, argumentowałbym, że dobrą praktyką jest umieszczenie jak najmniejszej liczby ograniczeń na poziomie generycznym, jaki jest wymagany do wykonania zadania. Ponieważ masz pełną kontrolę nad kodem wewnątrz generic, wiesz, czy używasz metod wymagających podstawowego typu IComparable. Tak więc, biorąc swój komentarz pod uwagę Osobiście według poniższych zasad:

  • Jeżeli nie spodziewasz rodzajową użyć wszelkich typów, które tylko implementują IComparable (tj dziedzictwo kod 1.0) i nie wzywają dowolny metody z wnętrza generic, które opierają się na parametrze IComparable, a następnie użyj tylko ograniczenia IComparable <>.

  • Jeśli użyciu typów, które tylko implementują IComparable następnie użyć tego ograniczenia tylko

  • Jeśli używasz metody, które wymagają IComparable parametr, ale nie przy użyciu typów, które tylko implementują IComparable następnie przy użyciu zarówno ograniczenia, jak w odpowiedzi Jeffa zwiększy wydajność, gdy użyjesz metod akceptujących typ ogólny.

Aby rozwinąć na trzecim reguły - załóżmy, że klasa piszesz jest następujący:

public class StrangeExample<T> where ... //to be decided 
{ 
    public void SortArray(T[] input) 
    { 
     Array.Sort(input); 
    } 

    public bool AreEqual(T a, T b) 
    { 
     return a.CompareTo(b) == 0; 
    } 
} 

I musimy zdecydować, jakie ograniczenia, aby umieścić na nim. Metoda SortArray wywołuje metodę Array.Sort, która wymaga przekazania tablicy, która zawiera obiekty implementujące funkcję IComparable. Dlatego koniecznością mieć IComparable ograniczenia:

public class StrangeExample<T> where T : IComparable 

Teraz klasa będzie działać poprawnie skompilować i jak tablica T jest ważny dla Array.sort i jest ważna metoda .CompareTo zdefiniowane w interfejsie. Jednakże, jeśli jesteś pewien, że nie będzie chciał korzystać z klasy z typem, który nie również wdrożenia IComparable <> Interfejs można przedłużyć ograniczenia do:

public class StrangeExample<T> where T : IComparable, IComparable<T> 

To oznacza, że ​​gdy AreEqual nazywa go wykorzysta szybszą, ogólną metodę CompareTo, a zobaczysz korzyści związane z wydajnością kosztem braku możliwości użycia go ze starymi typami .NET 1.0. Z drugiej strony, jeśli nie masz metody AreEqual, to nie ma żadnej przewagi w stosunku do wiązania IComparable <>, więc równie dobrze możesz go upuścić - i tak korzystasz tylko z implementacji IComparable.

+1

To nie do końca w porządku, ponieważ niektóre elementy BCL działają tylko z IComparable, a nie IComparable , takie jak Array.Sort i ArrayList.Sort. Naprawdę, ograniczenie powinno wymuszać oba interfejsy. –

+0

Dzięki Martin. Czy możesz wyjaśnić ostatnią regułę, którą napisałeś? Czy masz na myśli, że wydajność będzie lepsza, jeśli mam oba interfejsy i korzystam z ogólnego porównania? –

+0

Tak, ponieważ główną zaletą korzystania z ograniczenia IComparable <> jest wydajność, jeśli * musisz * obsługiwać typ IComparable, ponieważ używasz metod, które tego wymagają (np. Array.Sort), ale * możesz * wspierać Typ IComparable <>, ponieważ nie używasz żadnych starszych typów, które go nie implementują, najlepiej jest wymagać obu i używać obiektu jako najszybszego typu, w którym możesz. –

1

Użyłbym drugiego wiązania, ponieważ pozwoli ci na odwołanie się do mocno wpisanych członków interfejsu. Jeśli wybierzesz pierwszą opcję, musisz użyć rzutowania, aby użyć typu interfejsu.

2

To są dwa różne interfejsy. Przed .NET 2.0 nie było generycznych, więc było tylko IComparable. Wraz z .NET 2.0 pojawiły się generics i stało się możliwe stworzenie IComparable<T>. Robią dokładnie to samo. Zasadniczo IComparable jest przestarzałe, chociaż większość bibliotek tam rozpoznaje oba.

Aby kod był rzeczywiście kompatybilny, zaimplementuj oba, ale nawiąż jedno połączenie, więc nie musisz dwukrotnie pisać tego samego kodu.

5

Możesz zarówno ograniczenia, jak w:

where T : IComparable, IComparable<T> 

W ten sposób twój typ kompatybilny z większą liczbą użytkowników interfejsów IComparable. Ogólna wersja IComparable, , pomoże uniknąć boksowania, gdy T jest typem wartości i pozwala na silniejsze typowanie implementacji metod interfejsu. Wspieranie obu gwarantuje, że bez względu na to, jakiego interfejsu wymaga inny obiekt, twój obiekt może spełnić i dlatego ładnie współdziałać.

Na przykład: Array.Sort i ArrayList.Sort używają IComparable, a nie IComparable<T>.

Powiązane problemy