2013-06-04 9 views
8

Angenommen, Sie einen Datensatz mit einem überladenen GleichheitsoperatorNehmen Gleichheit in der generischen Sammlungen

TSomeRecord = record 
    Value : String; 
    class operator Equal(Left, Right : TSomeRecord) : Boolean; 
end; 

(Implementierung vergleicht String-Werte) haben. Wenn zwei Datensätze zu der Liste hinzugefügt werden, die basierend auf dem überladenen Operator gleich sind, würde ich erwarten, dass die Methode Contains in beiden Fällen true zurückgibt. Tatsächlich scheint die generische Liste jedoch nur den Speicherinhalt von Datensätzen zu vergleichen, anstatt den überladenen Gleichheitsoperator anzuwenden.

var 
    List : TList <TSomeRecord>; 
    Record1, 
    Record2 : TSomeRecord; 

begin 
Record1.Value := 'ABC'; 
Record2.Value := 'ABC'; 
List.Add(Record1); 

Assert(List.Contains(Record1)); 
Assert(List.Contains(Record2)); // <--- this is not true 
end; 

Ist dies das erwartete Verhalten? Irgendwelche Erklärungen?

+0

Wie funktioniert die Gleich Operator impl aussehen benutzen? Könnte mit http: // stackoverflow zusammenhängen.com/questions/8862807/list-and-contains-method –

+0

Im Allgemeinen unterstützen Datensätze den Operator '=' nicht, und es ist nicht möglich, in Code zu erkennen, ob ein bestimmter Typ dies unterstützt. Daher muss die Standardimplementierung einen einfachen Speichervergleich verwenden für alle Arten, die es nicht * a priori * kennt. –

+0

Dank @RobKennedy, dann wäre es nett, eine Gleichheitsbedingung für generische Typen zu haben, die die Existenz eines Gleichheitsoperators sicherstellen würde. – jpfollenius

Antwort

8

Angenommen, Sie haben im Konstruktor keinen Vergleich zu TList.Create angegeben, erhalten Sie TComparer<TSomeRecord>.Default als Vergleich. Und das ist ein Vergleich, der einen einfachen binären Vergleich unter Verwendung CompareMem durchführt.

Das ist in Ordnung für einen Datensatz voller Werttypen, ohne Polsterung. Aber ansonsten müssen Sie Ihre eigene Vergleichsfunktion angeben, wenn Sie die Liste instanziieren.

Wenn Sie die Details anzeigen möchten, ist der Standardvergleich für Datensätze in Generics.Defaults implementiert. Für größere Datensätze ist der Gleichheitsvergleich diese Funktion:

function Equals_Binary(Inst: PSimpleInstance; const Left, Right): Boolean; 
begin 
    Result := CompareMem(@Left, @Right, Inst^.Size); 
end; 

Für kleinere Aufzeichnungen gibt es eine Optimierung und Ihr Vergleich wird der 4-Byte-Vergleich sein. Das sieht wie folgt aus:

function Equals_I4(Inst: Pointer; const Left, Right: Integer): Boolean; 
begin 
    Result := Left = Right; 
end; 

Das ist ein bisschen komisch, aber es interpretiert die 4 Bytes des Datensatzes als eine 4-Byte-Ganzzahl und führt integer Gleichheitsvergleich. Mit anderen Worten, das gleiche wie CompareMem, aber effizienter.

Der Vergleich, den Sie verwenden möchten, könnte wie folgt aussehen:

TComparer<TSomeRecord>.Construct(
    function const Left, Right: TSomeRecord): Integer 
    begin 
    Result := CompareStr(Left.Value, Right.Value); 
    end; 
) 

Verwenden CompareText wenn Sie Groß- und Kleinschreibung wollen, und so weiter. Ich habe eine geordnete Vergleichsfunktion verwendet, weil das TList<T> will.

Die Tatsache, dass der Standard-Datensatzvergleich ein Gleichheitsvergleich ist, sagt Ihnen, dass Versuche, Listen von Datensätzen ohne Angabe eines eigenen Vergleichs zu sortieren, zu unerwarteten Ergebnissen führen werden.

Da der Standardvergleich einen Gleichheitsvergleich verwendet sagt Ihnen, dass es nicht völlig unvernünftig wäre ein Vergleich wie folgt zu verwenden:

TComparer<TSomeRecord>.Construct(
    function const Left, Right: TSomeRecord): Integer 
    begin 
    Result := ord(not (Left = Right)); 
    end; 
) 

Das für ungeordnete Operationen wie IndexOf oder Contains in Ordnung sein, aber offensichtlich nicht Verwenden Sie überhaupt für Sortieren, binäre Suche und so weiter.

+0

Hallo David, danke für die Erklärung. Bedeutet das, dass es im Allgemeinen nicht möglich ist, einen generischen Typ zu schreiben, der nur den Gleichheitsoperator verwendet, so dass die in den Typen integrierte Gleichheit verwendet wird, ohne einen benutzerdefinierten Vergleich anzugeben? – jpfollenius

+0

@Smasher Das ist richtig. Das Generics-Framework sucht einfach nicht nach Ihrem Gleichheitsoperator. In jedem Fall, wie ich in meinem Update erklärt habe, will die Listenklasse mehr als einen Gleichheitsvergleich. Es möchte die Elemente bestellen können. Das ist so, dass es sortieren kann. –

+0

Es gibt also keine Möglichkeit, die Gleichheit richtig zu definieren, ohne über die Reihenfolge nachzudenken - was macht in meinem Szenario keinen Sinn? – jpfollenius

3

Um das erwartete Verhalten zu erhalten, müssen Sie die Liste mit einem Vergleicher erstellen. In diesem Fall

sollten Sie

List := TList<TSomeRecord>.Create(TComparer<TSomeRecord>.Construct(
    function (const L, R : TSomeRecord) : Integer 
    begin 
    Result := CompareStr(L.Value, R.Value); 
    end)); 
+0

ja, es ist nur komisch für mich, dass ich die Gleichheitslogik zweimal angeben muss. Beachten Sie, dass der Vergleich, der nur "Ergebnis: = L = R" ausführt, gleich gut funktioniert. Deshalb war ich überrascht, dass die generischen Sammlungen ohne den Vergleich nicht damit umgehen können. – jpfollenius

+0

ah..korrekt. Also muss ich über das Ordnen nachdenken (was ich nicht brauche), um korrekte Gleichheitsvergleiche zu erhalten? Fühlt sich einfach nicht richtig an ... – jpfollenius

+1

@Smasher: Sie müssen über das Ordnen nachdenken, weil 'TList ' 'TComparer' zum Sortieren und Überprüfen der Gleichheit verwendet. –