2013-06-04 16 views
8

Si supponga di avere un record con un operatore di uguaglianza sovraccaricatoRecord uguaglianza in collezioni generici

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

(implementazione confronta i valori di stringa). Se si aggiungono due record all'elenco che sono uguali in base all'operatore sovraccarico, mi aspetto che il metodo Contains restituisca true in entrambi i casi. In realtà, l'elenco generico sembra semplicemente confrontare il contenuto di memoria dei record anziché applicare l'operatore di uguaglianza sovraccarico.

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; 

È questo il comportamento previsto? Qualche spiegazione?

+0

Come funziona l'impl Pari operatore assomigliano? Potrebbe essere correlato a http: // StackOverflow.it/questions/8862807/list-and-contiene-method –

+0

In generale, i record non supportano l'operatore '=', ed è impossibile rilevare in codice se un particolare tipo lo supporta, quindi l'implementazione di default deve usare il confronto della memoria semplice per tutti i tipi che non ha * a priori * conoscenza di. –

+0

Grazie a @RobKennedy, sarebbe bello avere un vincolo di uguaglianza sui tipi generici che garantirebbe l'esistenza di un operatore di uguaglianza. – jpfollenius

risposta

8

Supponendo che non sia stato specificato un confronto nel costruttore su TList.Create, si otterrà TComparer<TSomeRecord>.Default come comparatore. E questo è un comparatore che esegue un semplice confronto binario usando CompareMem.

Questo va bene per un record pieno di tipi di valore, senza padding. In caso contrario, dovrai fornire la tua funzione di confronto quando installi l'elenco.

Se si desidera esaminare i dettagli, il confronto predefinito per i record è implementato in Generics.Defaults. Per i record più grandi il confronto di uguaglianza è questa funzione:

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

per i record più piccoli c'è un'ottimizzazione e il vostro operatore di confronto sarà l'operatore di confronto 4 byte. Che assomiglia a questo:

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

Questo è un po 'strano, ma interpreta i 4 byte di vostro record come un intero 4 byte ed esegue confronto di uguaglianza intero. In altre parole, lo stesso di CompareMem, ma più efficiente.

L'operatore di confronto che si desidera utilizzare potrebbe essere simile a questo:

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

Usa CompareText se si vuole case insensitive, e così via. Ho usato una funzione di confronto ordinata perché è ciò che vuole TList<T>.

Il fatto che il confronto dei record predefinito sia un confronto di uguaglianza indica che i tentativi di ordinare elenchi di record senza specificare il proprio comparatore avranno risultati imprevisti.

Dato che l'operatore di confronto di default utilizza un confronto di uguaglianza ti dice che non sarebbe del tutto irragionevole utilizzare un operatore di confronto come questo:

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

Questo andrà bene per le operazioni non ordinate come IndexOf o Contains ma ovviamente non utilizzare affatto per l'ordinamento, la ricerca binaria e così via.

+0

Ciao David, grazie per la spiegazione. Ciò significa che in generale non è possibile scrivere un tipo generico che utilizza semplicemente l'operatore di uguaglianza in modo che l'uguaglianza incorporata nei tipi venga utilizzata senza specificare un confronto personalizzato? – jpfollenius

+0

@Smasher Proprio così. Il framework dei generici non cercherà il tuo operatore di uguaglianza. In ogni caso, come ho spiegato nel mio aggiornato, la classe list vuole più di un confronto di uguaglianza. Vuole essere in grado di ordinare gli elementi. Questo è così che può ordinare. –

+0

Quindi non c'è modo di definire correttamente l'uguaglianza senza pensare all'ordine - cosa che non ha senso nel mio scenario? – jpfollenius

3

Per ottenere il comportamento previsto, è necessario creare l'elenco con un comparatore.

In questo caso si consiglia di utilizzare

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

sì, è solo strano per me che devo specificare la logica di uguaglianza due volte. Si noti che il comparatore che esegue solo "Risultato: = L = R" funziona ugualmente bene. Ecco perché sono rimasto sorpreso dal fatto che le raccolte generiche non possano gestirle senza il confronto. – jpfollenius

+0

ah..corretto. Quindi devo pensare all'ordinamento (di cui non ho bisogno) per ottenere confronti di uguaglianza corretti? Non sembra giusto .... – jpfollenius

+1

@Smasher: devi pensare all'ordinazione perché 'TList ' usa il 'TComparer' per l'ordinamento e controlla l'uguaglianza. –