Wie vergleiche ich Werte generischer Typen?
Ich habe es auf ein Minimum reduziert:
public class Foo<T> where T : IComparable
{
private T _minimumValue = default(T);
public bool IsInRange(T value)
{
return (value >= _minimumValue); // <-- Error here
}
}
Der Fehler ist:
Der Operator '> =' kann nicht auf Operanden vom Typ 'T' und 'T' angewendet werden.
Was in aller Welt!? T
ist bereits eingeschränkt auf IComparable
, und selbst , wenn es um Wertetypen (Zwang where T: struct
), können wir immer noch nicht des Betreibers gelten <
, >
, <=
, >=
, ==
oder !=
. (Ich weiß, dass es Problemumgehungen Equals()
für ==
und gibt !=
, aber es hilft den relationalen Operatoren nicht).
Also zwei Fragen:
- Warum beobachten wir dieses seltsame Verhalten? Was hält uns davon ab , die Werte von generischen Typen zu vergleichen , die sind bekannt sein
IComparable
? Besiegt es nicht irgendwie den gesamten Zweck generischer Einschränkungen? - Wie löse ich das oder arbeite ich zumindest daran?
(Mir ist klar, dass es bereits eine Handvoll Fragen zu diesem scheinbar einfachen Problem gibt - aber keiner der Threads gibt eine erschöpfende oder praktikable Antwort, also hier.)
quelle
IComparable
den Vergleichsoperatoren Überlastung ist , dass es Situationen , in denenX.Equals(Y)
falsch zurückgeben sollte, aberX.CompareTo(Y)
sollte zurückkehren Null ( was darauf hindeutet , weder Element größer als das andere ist) [zB eineExpenseItem
natürliche Rangordnung in Bezug haben kannTotalCost
, und es kann sein , Keine natürliche Bestellung für Ausgabenposten, deren Kosten gleich sind, aber das bedeutet nicht, dass jeder Ausgabenposten, der 3.141,59 USD kostet, als gleichwertig mit jedem anderen Artikel angesehen werden sollte, der die gleichen Kosten verursacht.==
die logisch bedeuten könnten. In einigen Kontexten ist es möglichX==Y
, wahr zu sein, währendX.Equals(Y)
es falsch ist, und in anderen KontextenX==Y
könnte es falsch sein, solangeX.Equals(Y)
es wahr ist. Auch wenn die Betreiber für Schnittstellen, Überlastung überlastet werden könnte<
,<=
,>
und>=
in Bezug auf dieIComparable<T>
vielleicht einen Eindruck geben , dass==
und!=
würde auch in solchen Bedingungen überlastet werden. Wenn C #, wie vb, die Verwendung von==
Klassentypen verboten hätte, für die es nicht überladen war, wäre das vielleicht nicht so schlimm gewesen, aber ...==
zu verwenden, um sowohl einen überladbaren Gleichheitsoperator als auch einen nicht überladbaren Referenzgleichheitstest darzustellen.Problem mit Überlastung des Bedieners
Leider können Schnittstellen keine überladenen Operatoren enthalten. Versuchen Sie dies in Ihren Compiler einzugeben:
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); }
Ich weiß nicht, warum sie dies nicht zugelassen haben, aber ich vermute, dass dies die Sprachdefinition kompliziert und für Benutzer schwierig zu implementieren wäre.
Entweder das, oder die Designer mochten das Missbrauchspotential nicht. Stellen Sie sich zum Beispiel vor, Sie
>=
vergleichen aclass MagicMrMeow
. Oder sogar auf einemclass Matrix<T>
. Was bedeutet das Ergebnis für die beiden Werte?; Besonders wenn es eine Mehrdeutigkeit geben könnte?Die offizielle Abhilfe
Da die obige Schnittstelle nicht legal ist, haben wir die
IComparable<T>
Schnittstelle, um das Problem zu umgehen. Es implementiert keine Operatoren und macht nur eine Methode verfügbar.int CompareTo(T other);
Siehe http://msdn.microsoft.com/en-us/library/4d7sx9hd.aspx
Das
int
Ergebnis ist tatsächlich ein Tri-Bit oder ein Tri-Nary (ähnlich wie aBoolean
, jedoch mit drei Zuständen). Diese Tabelle erklärt die Bedeutung der Ergebnisse: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.
Workaround verwenden
Um das Äquivalent zu tun
value >= _minimumValue
, müssen Sie stattdessen schreiben:value.CompareTo(_minimumValue) >= 0
quelle
Wenn
value
null sein kann, kann die aktuelle Antwort fehlschlagen. Verwenden Sie stattdessen so etwas:Comparer<T>.Default.Compare(value, _minimumValue) >= 0
quelle
T
zuIComparable
. Aber dein Tipp hat mich trotzdem über den Berg gebracht.public bool IsInRange(T value) { return (value.CompareTo(_minimumValue) >= 0); }
Bei der Arbeit mit IComparable-Generika müssen alle Operatoren, die kleiner oder größer als sind, in Aufrufe von CompareTo konvertiert werden. Welchen Operator Sie auch verwenden würden, halten Sie die verglichenen Werte in derselben Reihenfolge und vergleichen Sie sie mit Null. (
x <op> y
Wirdx.CompareTo(y) <op> 0
, in denen<op>
ist>
,>=
usw.)Außerdem würde ich empfehlen, dass die von Ihnen verwendete generische Einschränkung lautet
where T : IComparable<T>
. IComparable für sich bedeutet, dass das Objekt mit allem verglichen werden kann. Ein Vergleich eines Objekts mit anderen Objekten des gleichen Typs ist wahrscheinlich besser geeignet.quelle
Statt
value >= _minimValue
VerwendungComparer
Klasse:public bool IsInRange(T value ) { var result = Comparer<T>.Default.Compare(value, _minimumValue); if ( result >= 0 ) { return true; } else { return false; } }
quelle
Comparer
wenn es bereits eine generische Einschränkung gibt,T
die implementiert werden mussIComparable
?Wie andere angegeben haben, muss die CompareTo-Methode explizit verwendet werden. Der Grund, warum man keine Schnittstellen mit Operatoren verwenden kann, ist, dass es einer Klasse möglich ist, eine beliebige Anzahl von Schnittstellen zu implementieren, ohne dass eine eindeutige Rangfolge zwischen ihnen besteht. Angenommen, man hat versucht, den Ausdruck "a = foo + 5;" zu berechnen. wenn foo sechs Schnittstellen implementiert hat, die alle einen Operator "+" mit einem ganzzahligen zweiten Argument definieren; Welche Schnittstelle soll für den Bediener verwendet werden?
Die Tatsache, dass Klassen mehrere Schnittstellen ableiten können, macht Schnittstellen sehr leistungsfähig. Leider zwingt es einen oft dazu, genauer zu sagen, was man eigentlich tun möchte.
quelle
IComparable
erzwingt nur eine aufgerufene FunktionCompareTo()
. Sie können also keinen der von Ihnen genannten Operatoren anwendenquelle
Ich konnte die Antwort von Peter Hedburg verwenden, um einige überladene Erweiterungsmethoden für Generika zu erstellen. Beachten Sie, dass die
CompareTo
Methode hier nicht funktioniert, da der TypT
unbekannt ist und diese Schnittstelle nicht darstellt. Trotzdem bin ich daran interessiert, Alternativen zu sehen.Ich hätte gerne in C # gepostet, aber der Konverter von Telerik schlägt bei diesem Code fehl. Ich bin mit C # nicht vertraut genug, um es zuverlässig manuell zu konvertieren. Wenn jemand die Ehre erweisen möchte, würde ich mich freuen, wenn dies entsprechend bearbeitet wird.
<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
--BEARBEITEN--
Ich konnte diese bis reinigen , indem Zwang
T
zuIComparable(Of T)
auf allen Methoden, wie OP angezeigt. Beachten Sie, dass diese Einschränkung erfordert TypT
zu implementierenIComparable(Of <type>)
als auch.<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
quelle