[fpc-pascal] Strange behavior in generics.collections TDictionary
Sven Barth
pascaldragon at googlemail.com
Wed Jan 26 22:49:27 CET 2022
Am 25.01.2022 um 18:48 schrieb Thomas Kurz via fpc-pascal:
> Consider the following code:
>
> ***
> PROGRAM project1;
>
> {$mode objfpc}
> {$longstrings on} // see output below
> {$modeswitch advancedrecords}
>
> USES Variants, Generics.Collections, SysUtils;
>
> TYPE TRecord = PACKED RECORD
> FID: NativeUInt;
> FKey: String;
> CONSTRUCTOR Create (AID: NativeUInt; AKey: String);
> END;
>
> CONSTRUCTOR TRecord.Create (AID: NativeUInt; AKey: String);
> BEGIN
> FID := AID;
> FKey := UpperCase (AKey);
> END;
>
> VAR
> Dict: SPECIALIZE TDictionary<TRecord,Variant>;
> i: SPECIALIZE TPair<TRecord,Variant>;
>
> BEGIN
> Dict := SPECIALIZE TDictionary<TRecord,Variant>.Create;
> Dict.Add (TRecord.Create (1, 'test'), 1);
> FOR i IN Dict DO Writeln (i.Key.FID, #9, i.Key.FKey, #9, i.Value);
> // ^^^ 1 TEST 1
> // -> so the entry is ok!
> Writeln (Dict.ContainsKey (TRecord.Create (1, 'test')));
> // ^^^ with longstrings on -> FALSE
> // with longstrings off -> TRUE
> Writeln (Dict.ContainsKey (TRecord.Create (1, 'TEST')));
> // ^^^ always FALSE
> Dict.Free;
> END.
> ***
>
> I'm very confused... I have no idea if I'm overseeing something or this is a bug in generics.collections or in the compiler.
This is by design, Delphi behaves the same here. The point is that the
default comparer used for records uses binary comparison and in case of
an AnsiString that will be a pointer while with a ShortString this will
be the string contents.
The solution is to create a comparer for your custom key:
=== code begin ===
type
TRecordComparer = class(TEqualityComparer<TRecord>)
function Equals(constref Left, Right: TRecord): Boolean; override;
function GetHashCode(constref Value: TRecord): UInt32; override;
end;
function TRecordComparer.Equals(constref Left, Right: TRecord): Boolean;
begin
Result := (Left.FID = Right.FID) and (Left.FKey = Right.FKey);
end;
function TRecordComparer.GetHashCode(constref Value: TRecord): UInt32;
begin
Result := BobJenkinsHash(PChar(Value.FKey)^, Length(Value.FKey) *
SizeOf(Char), 0);
Result := BobJenkinsHash(Value.FID, SizeOf(NativeUInt), Result);
end;
begin
// and then:
Dict := specialize
TDictionary<TRecord,Variant>.Create(TRecordComparer.Create);
// and now the remaining code works
end.
=== code end ===
Regards,
Sven
More information about the fpc-pascal
mailing list