Re: [fpc-pascal] Strange behavior in generics.collections TDictionary

2022-01-26 Thread Sven Barth via fpc-pascal

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;
   i: SPECIALIZE TPair;

BEGIN
   Dict := SPECIALIZE TDictionary.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)
    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.Create(TRecordComparer.Create);

  // and now the remaining code works
end.

=== code end ===

Regards,
Sven
___
fpc-pascal maillist  -  fpc-pascal@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal


[fpc-pascal] Strange behavior in generics.collections TDictionary

2022-01-26 Thread 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;
  i: SPECIALIZE TPair;

BEGIN
  Dict := SPECIALIZE TDictionary.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.

My system is:
Lazarus 2.2.0 (64 bit), FPC 3.2.2 (64 bit) on Windows 8.1 (64 bit)

___
fpc-pascal maillist  -  fpc-pascal@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal