Wie vergleiche ich Werte generischer Typen?

79

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!? Tist 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:

  1. 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?
  2. 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.)

gstercken
quelle

Antworten:

96

IComparableüberlastet den >=Bediener nicht. Du solltest benutzen

value.CompareTo(_minimumValue) >= 0
Faester
quelle
7
Großartig, das funktioniert (und erklärt es natürlich) - vielen Dank! Aber es ist ein bisschen unbefriedigend und lässt die Frage offen : Warum überlastet IComparable die Vergleichsoperatoren nicht? Ist dies eine bewusste und bewusste Entwurfsentscheidung aus gutem Grund - oder etwas, das bei der Gestaltung des Frameworks übersehen wurde? Immerhin ist 'x.CompareTo (y)> = 0' weniger lesbar als 'x> = y', nein?
Gstercken
Ich verstehe auf jeden Fall Ihren Standpunkt. Ich denke, das Problem ist, dass Operatoren statisch sind, was bedeutet, dass sie nicht in eine Schnittstelle passen. Ich werde nicht beurteilen, ob dies eine gute Wahl ist oder nicht, aber ich neige dazu zu denken, dass Methoden mit Eigennamen leichter zu lesen sind als Operatoren, wenn Typen nicht primitiv sind. Dies ist jedoch Geschmackssache.
Faester
5
@gstercken: Ein Problem bei IComparableden Vergleichsoperatoren Überlastung ist , dass es Situationen , in denen X.Equals(Y)falsch zurückgeben sollte, aber X.CompareTo(Y)sollte zurückkehren Null ( was darauf hindeutet , weder Element größer als das andere ist) [zB eine ExpenseItemnatürliche Rangordnung in Bezug haben kann TotalCost, 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.
Supercat
1
@gstercken: Grundsätzlich gibt es eine Reihe von Dingen, ==die logisch bedeuten könnten. In einigen Kontexten ist es möglich X==Y, wahr zu sein, während X.Equals(Y)es falsch ist, und in anderen Kontexten X==Ykönnte es falsch sein, solange X.Equals(Y)es wahr ist. Auch wenn die Betreiber für Schnittstellen, Überlastung überlastet werden könnte <, <=, >und >=in Bezug auf die IComparable<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 ...
Supercat
2
... leider hat C # beschlossen, das Token ==zu verwenden, um sowohl einen überladbaren Gleichheitsoperator als auch einen nicht überladbaren Referenzgleichheitstest darzustellen.
Supercat
35

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 a class MagicMrMeow. Oder sogar auf einem class 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 intErgebnis ist tatsächlich ein Tri-Bit oder ein Tri-Nary (ähnlich wie a Boolean, 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
Merlyn Morgan-Graham
quelle
2
Ah, richtig - macht Sinn. Ich habe vergessen, dass Schnittstellen in C # Operatoren nicht überladen können.
Gstercken
29

Wenn valuenull sein kann, kann die aktuelle Antwort fehlschlagen. Verwenden Sie stattdessen so etwas:

Comparer<T>.Default.Compare(value, _minimumValue) >= 0
Peter Hedberg
quelle
1
Danke für den Tipp. Ich brauchte dies für einige Erweiterungsmethoden, an denen ich arbeitete. Siehe unten.
InteXX
1
OK habe es. Ich war beschränke nicht Tzu IComparable. Aber dein Tipp hat mich trotzdem über den Berg gebracht.
InteXX
6
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> yWird x.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.

David Yaw
quelle
3

Statt value >= _minimValueVerwendung ComparerKlasse:

public bool IsInRange(T value ) {
    var result = Comparer<T>.Default.Compare(value, _minimumValue);
    if ( result >= 0 ) { return true; }
    else { return false; }
}
TcKs
quelle
Wny die Verwendung von a einführen, Comparerwenn es bereits eine generische Einschränkung gibt, Tdie implementiert werden muss IComparable?
Fredrik Mörk
@Fredrik generische Einschränkungen neigen dazu, sich aufzubauen. Ich bin damit einverstanden, sie hier wegzulassen.
Marc Gravell
2

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.

Superkatze
quelle
1
Ich glaube nicht, dass MI bei überlasteten Operatoren mehr ein Problem ist als bei normalen Methoden. Sie sind nur eine lustige Syntax für Methoden. Sie können dieses Problem also lösen, indem Sie dieselben Regeln verwenden, um sie als reguläre Methoden für Schnittstellen zu lösen. Eines der Ziele des C # -Designs war es, C ++ - Programmierern etwas vertraut zu sein, und überladene Operatoren wurden in dieser Sprache zur Hölle und zurück missbraucht. Ich vermute, die Designer bevorzugten benannte Methoden, die Sie dazu zwingen, eine Art Dokumentation für die Absicht dieser Methoden zu geben.
Merlyn Morgan-Graham
1

IComparableerzwingt nur eine aufgerufene Funktion CompareTo(). Sie können also keinen der von Ihnen genannten Operatoren anwenden

parapura rajkumar
quelle
0

Ich konnte die Antwort von Peter Hedburg verwenden, um einige überladene Erweiterungsmethoden für Generika zu erstellen. Beachten Sie, dass die CompareToMethode hier nicht funktioniert, da der Typ Tunbekannt 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 Tzu IComparable(Of T)auf allen Methoden, wie OP angezeigt. Beachten Sie, dass diese Einschränkung erfordert Typ Tzu implementieren IComparable(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
InteXX
quelle