Delphi TPair Exception

2020-07-15 12:35发布

问题:

I have this spike to test TPair. You can copy+paste on a new Delphi XE Console-app. I have marked the line with the exception:

Project Project1.exe raised exception class EAccessViolation with message 'Access violation at address 0045042D in module 'Project1.exe'. Read of address A9032D0C.

Any Idea ?

Thanks.

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Generics.Defaults,
  Generics.Collections;

type
  TProduct = class
  private
    FName: string;
    procedure SetName(const Value: string);
  published
  public
    property Name: string read FName write SetName;
  end;

type
  TListOfProducts = TObjectDictionary<TProduct, Integer>;

{ TProduct }

procedure TProduct.SetName(const Value: string);
begin
  FName := Value;
end;


var
  MyDict: TListOfProducts;
  MyProduct1: TProduct;
  MyProduct2: TProduct;
  MyProduct3: TProduct;
  APair: TPair<TProduct, Integer>;
  aKey: string;

begin
  try
    MyDict := TListOfProducts.Create([doOwnsKeys]);
    MyProduct1 := TProduct.Create;
    MyProduct1.Name := 'P1';
    MyProduct2 := TProduct.Create;
    MyProduct2.Name := 'P2';
    MyProduct3 := TProduct.Create;
    MyProduct3.Name := 'P3';

    MyDict.Add(MyProduct1, 1);
    MyDict.Add(MyProduct2, 2);
    MyDict.Add(MyProduct3, 3);

    APair := MyDict.ExtractPair(MyProduct1);
    Writeln(APair.Key.Name);   // <--- Error is Here.
    Writeln(IntToStr(APair.Value));

    Readln(aKey);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

回答1:

This is a Delphi bug. TDictionary<TKey,TValue>.ExtractPair does not assign Result.

RRUZ located the bug in QC.

The code reads:

function TDictionary<TKey,TValue>.ExtractPair(const Key: TKey): TPair<TKey,TValue>;
var
  hc, index: Integer;
begin
  hc := Hash(Key);
  index := GetBucketIndex(Key, hc);
  if index < 0 then
    Exit(TPair<TKey,TValue>.Create(Key, Default(TValue)));

  DoRemove(Key, hc, cnExtracted);
end;

Result should be assigned when the call to DoRemove is made.

It's quite hard to work around this bug. ExtractPair is the only way to get an item out of the dictionary without destroying the key and so you have to call it. But since it won't return the extracted item, you need to first read the item, remember the value, and then call ExtractPair.