1

TPersistent オブジェクトのコレクションがあります。メモリの場所 (メイン インデックスとして) とプロパティ (rtti を使用) によってインデックスを作成したいと考えています。コレクションには、複数のプロパティ ベースのインデックス (異なるプロパティに基づく) が含まれる場合があります。

この問題を解決するための戦略とデータ構造は何ですか?プロパティベースのインデックスを処理するときに、アクセス違反なしでコレクションから無効なオブジェクト (既に破棄されているオブジェクト) を削除できますか?

編集:物事を明確にするために、この種のインターフェース(クラスとメソッド)を実装したいと考えています:

type
  TMyItem=class(TPersistent)
  public
    property HasProp[Name: string]: Boolean read GetHasProp;
    property PropValue[Name: string]: Variant read GetPropValue write SetPropValue;
  end;

  TMyCollection=class(TPersistent)
  private
    FMainList: TSortedList;
    FIndexes : TIndexes;
  public
    function Add(AItem: TMyItem): Integer;
    procedure Extract(AItem: TMyItem);
    procedure Delete(AItem: TMyItem);

    // and new sorted list based on the given property name to the FIndexes
    procedure AddIndex(const APropName: string);
    // remove sorted list correspoding to the given property name from FIndexes
    procedure RemoveIndex(const APropName: string);

    // just to see if the item is in the collection
    function Find(AItem: TMyItem): Boolean;
    // try to find first item which property specified by APropName has value specified by APropValue
    function Find(const APropName: string; const APropValue: Variant; var AItem: TMyItem): Boolean;
  end;

FMainList には、TMyItem インスタンスへのポインタのリストが含まれています。ポインターを NativeInt にキャストすることによってソートされます。したがって、無効なオブジェクトが見つかっても問題ありません。ただし、プロパティ ベースのインデックスでは、プロパティ値に基づいて TMyItems を並べ替えています。したがって、プロパティ値を取得する必要があるため、無効な TMyItem (既に破棄されているもの) のエントリを見つけようとすると、EAccessViolation が発生します。

これを解決するための私の現在の考えは、各プロパティ ベースのインデックス内の TMyItem の位置を、FMainList に格納されているメイン レコードに格納することです。ただし、このアプローチでは、新しいアイテムが追加または削除されるたびにすべての位置を更新する必要もあります。これは私が避けたいことです。それで、他にもっと良いメカニズムはありますか?

4

1 に答える 1

2

これらの種類の質問は、一般に、インデックスの保存または計算のどちらかを選択することにつながります。これは、速度とメモリのどちらを選択するかと同義です。また、使用法にもよりますが、これらのFindルーチンは頻繁に呼び出されますか?

すでにおっしゃるように、各インデックスを別々の配列に保存すると、あらゆる種類の同期の問題が発生し、メモリが大幅に余分に必要になります。

個人的には、リクエストに応じて各インデックスを計算/取得します。確かに、すべてのアイテムを反復処理するには時間がかかりますが、カウントが10万未満、またはそれ以上の数にとどまる場合は、遅延が発生することはないと確信しています。また、menjarazのコメントのようにデザインをTCollection作成する場合、削除されたアイテムについて心配する必要はありません。アイテムはありません。

アップデート:

RTTIの使用が必要な任意の名前のプロパティで値を検索するため、この反復タスクは少し遅くなる可能性があります。それを排除するために、私はあなたのためにこの最適化を書きました。これは、プロパティを持たない検索操作の項目を除外することに基づいています。このために、コレクションに含まれるプロパティ名と、それらが属するクラスを格納します。唯一の制限は、重複するプロパティ名が存在しないことですが、とにかく共通の祖先で同じプロパティ名を持つクラスを組み合わせると思います。(ただし、2番目が最初から継承されている限り、同じプロパティ名のクラスを追加することは可能です。)

unit MyCollection;

interface

uses
  Classes, TypInfo;

type
  TMyItem = class(TCollectionItem)
  end;

  TMyCollection = class(TOwnedCollection)
  private
    FPrevItemClass: TClass;
    FPropList: TStringList;
    function GetItem(Index: Integer): TMyItem;
    procedure RegisterItemClass(AClass: TClass);
    procedure SetItem(Index: Integer; Value: TMyItem);
  protected
    procedure Notify(Item: TCollectionItem;
      Action: TCollectionNotification); override;
  public
    constructor Create(AOwner: TPersistent); virtual;
    destructor Destroy; override;
    function Find(AItem: TMyItem): Boolean; overload;
    function Find(const APropName: String; AValue: Variant;
      var AItem: TMyItem): Boolean; overload;
    property Items[Index: Integer]: TMyItem read GetItem write SetItem;
      default;
  end;

implementation

resourcestring
  SDupPropName = 'Duplicate property name. Only classes with unique ' +
                 'property names are supposed to be added to this collection.';

{ TMyCollection }

constructor TMyCollection.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner, TMyItem);
  FPropList := TStringList.Create;
  RegisterItemClass(TMyItem);
end;

destructor TMyCollection.Destroy;
begin
  FPropList.Free;
  inherited Destroy;
end;

function TMyCollection.Find(AItem: TMyItem): Boolean;
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
    if Items[I] = AItem then
    begin
      Result := True;
      Exit;
    end;
  Result := False;
end;

function TMyCollection.Find(const APropName: String; AValue: Variant;
  var AItem: TMyItem): Boolean;
var
  I: Integer;
  ItemClass: TClass;
begin
  Result := False;
  if FPropList.Find(APropName, I) then
  begin
    ItemClass := TClass(FPropList.Objects[I]);
    for I := 0 to Count - 1 do
      if Items[I] is ItemClass then
        if GetPropValue(Items[I], APropName, False) = AValue then
        begin
          AItem := Items[I];
          Result := True;
        end;
  end;
end;

function TMyCollection.GetItem(Index: Integer): TMyItem;
begin
  Result := TMyItem(inherited GetItem(Index));
end;

procedure TMyCollection.Notify(Item: TCollectionItem;
  Action: TCollectionNotification);
begin
  inherited Notify(Item, Action);
  if Action = cnAdded then
    if Item.ClassType <> FPrevItemClass then
      if FPropList.IndexOfObject(TObject(Item.ClassType)) = -1 then
        RegisterItemClass(Item.ClassType)
end;

procedure TMyCollection.RegisterItemClass(AClass: TClass);
var
  PropCount: Integer;
  PropList: PPropList;
  I: Integer;
  J: Integer;
  PropName: String;
begin
  PropCount := GetTypeData(AClass.ClassInfo)^.PropCount;
  if PropCount > 0 then
  try
    GetPropList(AClass.ClassInfo, PropList);
    for I := 0 to PropCount - 1 do
    begin
      PropName := PropList[I].Name;
      if not FPropList.Find(PropName, J) then
      begin
        FPropList.AddObject(PropName, TObject(AClass));
      end
      else
        if not AClass.InheritsFrom(TClass(FPropList.Objects[J])) then
          raise EInvalidOperation.Create(SDupPropName);
    end;
    FPrevItemClass := AClass;
  finally
    FreeMem(PropList);
  end;
end;

procedure TMyCollection.SetItem(Index: Integer; Value: TMyItem);
begin
  inherited SetItem(Index, Value);
end;

end.
于 2012-01-07T12:01:37.920 に答える