[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