Jak porównać wartości typów ogólnych?
Zredukowałem to do minimalnej próbki:
public class Foo<T> where T : IComparable
{
private T _minimumValue = default(T);
public bool IsInRange(T value)
{
return (value >= _minimumValue); // <-- Error here
}
}
Błąd:
Operator „> =” nie może być stosowany do operandów typu „T” i „T”.
Co u licha !? T
są już ograniczone do IComparable
, a nawet kiedy ograniczanie go do typów wartości ( where T: struct
), nadal nie może zastosować każdy z operatorów <
, >
, <=
, >=
, ==
lub !=
. (Wiem, że Equals()
istnieją obejścia dotyczące ==
i !=
, ale nie pomaga to operatorom relacyjnym).
A więc dwa pytania:
- Dlaczego obserwujemy to dziwne zachowanie? Co powstrzymuje nas przed porównaniem wartości typów ogólnych, o których wiadomo, że są
IComparable
? Czy nie jest to w jakiś sposób sprzeczne z celem ogólnych ograniczeń? - Jak to rozwiązać lub przynajmniej obejść?
(Zdaję sobie sprawę, że jest już kilka pytań związanych z tym pozornie prostym problemem - ale żaden z wątków nie daje wyczerpującej ani praktycznej odpowiedzi, więc tutaj.)
c#
.net
generics
icomparable
gstercken
źródło
źródło
IComparable
przeciążenia operatorów porównania jest to, że istnieją sytuacje, w którychX.Equals(Y)
należy return false, aleX.CompareTo(Y)
powinien wrócić zero (co sugeruje ani elementem jest większa niż druga) [npExpenseItem
może mieć naturalną kolejność względemTotalCost
, i nie może być brak naturalnego uporządkowania pozycji kosztowych, których koszt jest taki sam, ale nie oznacza to, że każda pozycja wydatkowa, która kosztuje 3141,59 USD, powinna być traktowana jako ekwiwalent każdej innej pozycji, która kosztuje tyle samo.==
mogą logicznie oznaczać. W niektórych kontekstach możliwe jest,X==Y
aby być prawdą, podczas gdyX.Equals(Y)
jest fałszem, a podczas gdy w innych kontekstachX==Y
może być fałszem, gdyX.Equals(Y)
jest prawdziwe. Nawet jeśli operatorzy mogą być przeciążone dla interfejsów, przeciążenia<
,<=
,>
a>=
pod względemIComparable<T>
może dać wrażenie, że==
i!=
będzie również przeciążone w takich warunkach. Gdyby C #, podobnie jak vb, zabronił używania==
typów klas, dla których nie był przeciążony, mogłoby to nie być takie złe, ale ...==
do reprezentowania zarówno przeciążalnego operatora równości, jak i nieprzeciążalnego testu równości odniesienia.Problem z przeciążeniem operatora
Niestety interfejsy nie mogą zawierać przeciążonych operatorów. Spróbuj wpisać to w swoim kompilatorze:
public interface IInequalityComaparable<T> { bool operator >(T lhs, T rhs); bool operator >=(T lhs, T rhs); bool operator <(T lhs, T rhs); bool operator <=(T lhs, T rhs); }
Nie wiem, dlaczego na to nie pozwolili, ale domyślam się, że skomplikowało to definicję języka i byłoby trudne dla użytkowników do poprawnej implementacji.
Albo to, albo projektanci nie lubili możliwości nadużyć. Na przykład wyobraź sobie, że robisz
>=
porównanie naclass MagicMrMeow
. Lub nawet naclass Matrix<T>
. Co oznacza wynik dla tych dwóch wartości ?; Zwłaszcza, gdy może być niejasność?Oficjalne obejście
Ponieważ powyższy interfejs nie jest legalny, mamy
IComparable<T>
interfejs umożliwiający obejście problemu. Nie implementuje operatorów i udostępnia tylko jedną metodę,int CompareTo(T other);
Zobacz http://msdn.microsoft.com/en-us/library/4d7sx9hd.aspx
int
Wynik jest rzeczywiście tri-bitowy lub tri-nary (podobny doBoolean
, ale z trzech stanów). Ta tabela wyjaśnia znaczenie wyników:Value Meaning Less than zero This object is less than the object specified by the CompareTo method. Zero This object is equal to the method parameter. Greater than zero This object is greater than the method parameter.
Korzystanie z obejścia
Aby zrobić odpowiednik
value >= _minimumValue
, musisz zamiast tego napisać:value.CompareTo(_minimumValue) >= 0
źródło
Jeśli
value
może być puste, bieżąca odpowiedź może się nie powieść. Zamiast tego użyj czegoś takiego:Comparer<T>.Default.Compare(value, _minimumValue) >= 0
źródło
T
sięIComparable
. Mimo wszystko twoja napiwek doprowadziła mnie do szaleństwa.public bool IsInRange(T value) { return (value.CompareTo(_minimumValue) >= 0); }
Podczas pracy z IComparable generics wszystkie operatory mniejsze niż / większe niż muszą zostać przekonwertowane na wywołania do CompareTo. Niezależnie od tego, jakiego operatora użyjesz, zachowaj porównywane wartości w tej samej kolejności i porównaj je z zerem. (
x <op> y
Staniex.CompareTo(y) <op> 0
, w którym<op>
znajduje się>
,>=
etc.)Poleciłbym również, aby ogólne ograniczenie, którego używasz, było
where T : IComparable<T>
. IComparable sam w sobie oznacza, że obiekt można porównać z czymkolwiek, porównywanie obiektu z innymi tego samego typu jest prawdopodobnie bardziej odpowiednie.źródło
Zamiast klasy
value >= _minimValue
użytkowejComparer
:public bool IsInRange(T value ) { var result = Comparer<T>.Default.Compare(value, _minimumValue); if ( result >= 0 ) { return true; } else { return false; } }
źródło
Comparer
gdy istnieje już ogólne ograniczenie, któreT
należy wdrożyćIComparable
?Jak powiedzieli inni, należy jawnie użyć metody CompareTo. Powodem, dla którego nie można używać interfejsów z operatorami, jest to, że klasa może zaimplementować dowolną liczbę interfejsów bez wyraźnego rankingu między nimi. Załóżmy, że ktoś próbował obliczyć wyrażenie „a = foo + 5;” gdy foo zaimplementowało sześć interfejsów, z których wszystkie definiują operator „+” z drugim argumentem będącym liczbą całkowitą; jakiego interfejsu powinien używać operator?
Fakt, że klasy mogą wyprowadzać wiele interfejsów, sprawia, że interfejsy są bardzo potężne. Niestety, często zmusza to do wyraźniejszego wyrażenia tego, co naprawdę chce się zrobić.
źródło
IComparable
wymusza tylko funkcję o nazwieCompareTo()
. Nie możesz więc zastosować żadnego z wymienionych operatorówźródło
Udało mi się użyć odpowiedzi Petera Hedburga do stworzenia przeciążonych metod rozszerzających dla typów generycznych. Zauważ, że
CompareTo
metoda nie działa tutaj, ponieważ typT
jest nieznany i nie przedstawia tego interfejsu. To powiedziawszy, jestem zainteresowany znalezieniem alternatyw.Chciałbym napisać w C #, ale konwerter Telerika nie działa w tym kodzie. Nie znam języka C # na tyle, aby niezawodnie przekonwertować go ręcznie. Jeśli ktoś chciałby zrobić zaszczyt, byłbym zadowolony, gdyby został odpowiednio zredagowany.
<Extension> <DebuggerStepThrough> Public Sub RemoveDuplicates(Of T)(Instance As List(Of T)) Instance.RemoveDuplicates(Function(X, Y) Comparer(Of T).Default.Compare(X, Y)) End Sub <Extension> <DebuggerStepThrough> Public Sub RemoveDuplicates(Of T)(Instance As List(Of T), Comparison As Comparison(Of T)) Instance.RemoveDuplicates(New List(Of Comparison(Of T)) From {Comparison}) End Sub <Extension> <DebuggerStepThrough> Public Sub RemoveDuplicates(Of T)(Instance As List(Of T), Comparisons As List(Of Comparison(Of T))) Dim oResults As New List(Of Boolean) For i As Integer = 0 To Instance.Count - 1 For j As Integer = Instance.Count - 1 To i + 1 Step -1 oResults.Clear() For Each oComparison As Comparison(Of T) In Comparisons oResults.Add(oComparison(Instance(i), Instance(j)) = 0) Next oComparison If oResults.Any(Function(R) R) Then Instance.RemoveAt(j) End If Next j Next i End Sub
--EDYTOWAĆ--
Udało mi się to wyczyścić, ograniczając się
T
doIComparable(Of T)
wszystkich metod, jak wskazano w OP. Zauważ, że to ograniczenie wymaga również typuT
do zaimplementowaniaIComparable(Of <type>)
.<Extension> <DebuggerStepThrough> Public Sub RemoveDuplicates(Of T As IComparable(Of T))(Instance As List(Of T)) Instance.RemoveDuplicates(Function(X, Y) X.CompareTo(Y)) End Sub
źródło