8

オーバーロードされた等値演算子を含むレコードがあるとします

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

(実装は文字列値を比較します)。オーバーロードされた演算子に基づいて等しい 2 つのレコードをリストに追加する場合、Containsメソッドはどちらの場合も true を返すと予想されます。しかし実際には、ジェネリック リストは、オーバーロードされた等値演算子を適用する代わりに、レコードのメモリ コンテンツを比較しているように見えます。

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;

これは予想される動作ですか?説明はありますか?

4

2 に答える 2

8

コンストラクターで比較子を指定しなかったと仮定すると、比較子としてTList.Create取得TComparer<TSomeRecord>.Defaultされます。そして、それは を使用して単純なバイナリ比較を実行する比較子ですCompareMem

パディングなしで、値の型でいっぱいのレコードには問題ありません。ただし、それ以外の場合は、リストをインスタンス化するときに独自の比較関数を提供する必要があります。

詳細を確認したい場合は、レコードのデフォルトの比較機能が に実装されていGenerics.Defaultsます。より大きなレコードの場合、等値比較子は次の関数です。

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

小さいレコードの場合は最適化が行われ、比較子は 4 バイトの比較子になります。それは次のようになります。

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

これは少し奇妙ですが、レコードの 4 バイトを 4 バイトの整数として解釈し、整数の等価比較を実行します。つまり、 と同じCompareMemですが、より効率的です。

使用する比較子は次のようになります。

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

CompareText大文字と小文字を区別しない場合などに使用します。それが必要なので、順序付き比較関数を使用しましたTList<T>

デフォルトのレコード比較が等価比較であるという事実は、独自の比較子を指定せずにレコードのリストをソートしようとすると、予期しない結果になることを示しています。

デフォルトの比較子が等価比較を使用することを考えると、次のような比較子を使用することはまったく不合理ではないことがわかります。

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

IndexOforのような順序付けられていない操作には問題Containsありませんが、並べ替えや二分探索などにはまったく使用されません。

于 2013-06-04T14:43:59.960 に答える