calling clear on a set will generate an OnChange event in which you can't call ToArray()

Issue #399 resolved
Merijn Bosma created an issue

See below code snippet, the call to ToArray() in the OnSetChanged event handler throws an range check error after the call to s.Clear()

procedure TForm3.FormCreate(Sender: TObject);
begin
 var s := TCollections.CreateSet<string>();

 s.OnChanged.Add(OnSetChanged);
 s.Add('a');
 s.Add('b');
 s.Clear();
end;

procedure TForm3.OnSetChanged(Sender: TObject; const Item: string; Action: TCollectionChangedAction);
begin
 var b := TFoldedHashSet<string>(Sender).ToArray;
end;

It seems to be due to fHastTable.ClearCount (line 8) from this snippet from Spring.Collection.Sets

procedure THashSet<T>.Clear;
var
  item: PItem;
  i: Integer;
begin
  if Assigned(Notify) then
  begin
    fHashTable.ClearCount;
    item := PItem(fHashTable.Items);
    for i := 1 to fHashTable.ItemCount do //FI:W528
      if item.HashCode >= 0 then
        Notify(Self, item.Item, caRemoved);
  end;

  fHashTable.Clear;
end;

It shows up in different forms, if I change the event handler from the original snippet to (add .Ordered)

procedure TForm3.OnSetChanged(Sender: TObject; const Item: string; Action: TCollectionChangedAction);
begin
 var b := TFoldedHashSet<string>(Sender).Ordered.ToArray;
end;

It breaks on line 10 in this snippet from Spring.Collections.Sets (fHashTable.Count is 0, thus Length(Result) = 0, thus you can’t target Result[targetIndex] on line 10).

function THashSet<T>.ToArray: TArray<T>;
var
  sourceIndex, targetIndex: Integer;
begin
  SetLength(Result, fHashTable.Count);
  targetIndex := 0;
  for sourceIndex := 0 to fHashTable.ItemCount - 1 do
    if TItems(fHashTable.Items)[sourceIndex].HashCode >= 0 then
    begin
      Result[targetIndex] := TItems(fHashTable.Items)[sourceIndex].Item;
      Inc(targetIndex);
    end;
end;

Comments (2)

  1. Log in to comment