delphitreeassociative-arraytdictionarydelphi-xe8

Access Violation in TDictionary<Variant, Record>


I just wrote a very simple class to test the TDictionary<> Class in Delphi XE8.

When i try to show the records that i added, it brings me an Access Violation error, i don't understand why?

Here is my class

unit Unit3;

interface

 uses
  Classes, System.SysUtils, System.Types, REST.Types, System.JSON, Data.Bind.Components,
  System.RegularExpressions, System.Variants,
  Generics.Collections, FMX.Dialogs {$IFDEF DEBUG}, CodeSiteLogging{$ENDIF};

type

  TAArray2 = class;

  PTRec=^TRec;

  TRec = class
  public
    Key : Variant;
    isRequired : boolean;
    Value : Variant;
    OldValue : Variant;
    JSON : string;
    Items : TAArray2;
    procedure Add(Key : Variant ; Value: TRec);
  end;

   TAArray2 = class(TDictionary<Variant, TRec>)
   private
     function Get(Index: variant): TRec;
   public
     destructor Destroy; override;
     procedure Add(Key : Variant ; Value: TRec);
     property Items[Cle : Variant]: TRec read Get; default;
   end;

implementation

procedure TRec.Add(Key : Variant ; Value: TRec);
begin
  if not(assigned(items)) then
    self.Items := TAArray2.Create;
  Items.Add( Key, Value);
  showmessage(inttostr(items.Count)); // this show 1 means items is instanciate and contain the proper data
end;

function TAArray2.Get(Index: Variant): TRec;
begin
  Result := inherited items[Index]
end;

end.

Then i'm using this code to test it : (a form with 1 TButton and 1 TMemo)

procedure TForm1.ShowAssocArray2(AAA : TAArray2 ; Level : integer);
var
  s : string;
  MyRec : TRec;
begin
  for MyRec in AAA.Values Do
  begin
    FillChar(s, Level * 4, ' ');
    memo1.Lines.Add(s + string(MyRec.Key) + ' = ' + string(MyRec.Value));
    if MyRec.Items.Count > 0 then  // ERROR HERE
      ShowAssocArray2(MyRec.items, Level + 1);   // recursive for childrens
  end;
end;

procedure TForm1.Button4Click(Sender: TObject);
var
  MyList : TAArray2;
  MyRec : TRec;
  i : Integer;
begin
  MyList := TAArray2.Create;
  for i := 0 to 9 do
  begin
    MyRec := TRec.Create;
    MyRec.Value := 'Value_' + inttostr(i);
    MyRec.Key := 'No_' + inttostr(i);
    MyList.Add(MyRec.Key, MyRec);
  end;
  // subitem
  MyRec := TRec.Create;
  MyRec.Value := 'test' + inttostr(i);
  MyRec.Key := 'test' + inttostr(i);
  MyList.Items['No_3'].Add('Extra', MyRec);

  memo1.Lines.Add('Nb of Record : ' + inttostr(MyList.Count));

  ShowAssocArray2(MyList, 0);

end;

I tried many way : MyRec.Items.Count or MyRec.Values.Count or MyRec.Items.Values.count... i always have an error i don't understand why?


Solution

  • This is a stripped version which executes:

    program Project20;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils,Generics.Collections,StrUtils;
    
    
    type
      TAArray2 = class;
    
      TRec = class
      public
        Key : Variant;
        Value : Variant;
        Items : TAArray2;
        procedure Add(Key : Variant ; Value: TRec);
      end;
    
       TAArray2 = class(TDictionary<Variant, TRec>)
       private
         function Get(Index: variant): TRec;
       public
         destructor Destroy; override;
         //procedure Add(Key : Variant ; Value: TRec);
         property Items[Cle : Variant]: TRec read Get; default;
       end;
    
    procedure TRec.Add(Key : Variant ; Value: TRec);
    begin
      if not(assigned(items)) then
        self.Items := TAArray2.Create;
      Items.Add( Key, Value);
      WriteLn(inttostr(items.Count)); // this show 1 means items is instanciate and contain the proper data
    end;
    
    destructor TAArray2.Destroy;
    begin
    
      inherited;
    end;
    
    function TAArray2.Get(Index: Variant): TRec;
    begin
      Result := inherited items[Index]
    end;
    
    procedure ShowAssocArray2(AAA : TAArray2 ; Level : integer);
    var
      s : string;
      MyRec : TRec;
    begin
      s := DupeString(' ',Level * 4);
      for MyRec in AAA.Values Do
      begin
        WriteLn(s + string(MyRec.Key) + ' = ' + string(MyRec.Value));
        if Assigned(MyRec.Items) then // <-- Test if Items is assigned
         if MyRec.Items.Count > 0 then 
          ShowAssocArray2(MyRec.items, Level + 1);   // recursive for childrens
      end;
    end;
    
    var
      MyList : TAArray2;
      MyRec : TRec;
      i : Integer;
    begin
      MyList := TAArray2.Create;
      for i := 0 to 9 do
      begin
        MyRec := TRec.Create;
        MyRec.Value := 'Value_' + inttostr(i);
        MyRec.Key := 'No_' + inttostr(i);
        MyList.Add(MyRec.Key, MyRec);
      end;
      // subitem
      MyRec := TRec.Create;
      MyRec.Value := 'test' + inttostr(i);
      MyRec.Key := 'test' + inttostr(i);
      MyList.Items['No_3'].Add('Extra', MyRec);
    
      WriteLn('Nb of Record : ' + inttostr(MyList.Count));
    
      ShowAssocArray2(MyList, 0);
      ReadLn;
    end.
    

    The call to FillChar() was replaced with DupeString(), since no memory was allocated for the string before FillChar().

    There is also a test for Assigned(MyRec.Items) that resolves the case when Items are unassigned, which was the cause of your access violation.

    This executes, but I have not analyzed if the result is what you want. Don't forget to make sure there are no memory leaks as well.

    The printout:

    1
    Nb of Record : 10
    No_4 = Value_4
    No_3 = Value_3
        test10 = test10
    No_9 = Value_9
    No_7 = Value_7
    No_8 = Value_8
    No_1 = Value_1
    No_2 = Value_2
    No_5 = Value_5
    No_0 = Value_0
    No_6 = Value_6