web-dev-qa-db-ja.com

レコードのDelphi TList

レコードの一時的なリストを保存する必要があり、TListがこれを行うのに良い方法だと考えていましたか?ただし、TListを使用してこれを行う方法は不明であり、これが最善かどうか、またこれを行う方法の例があるかどうか疑問に思っていましたか?

31
colin

最も簡単な方法は、TListの独自の子孫を作成することです。デモ用の簡単なサンプルコンソールアプリを次に示します。

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
  PMyRec=^TMyRec;
  TMyRec=record
    Value: Integer;
    AByte: Byte;
  end;

  TMyRecList=class(TList)
  private
    function Get(Index: Integer): PMyRec;
  public
    destructor Destroy; override;
    function Add(Value: PMyRec): Integer;
    property Items[Index: Integer]: PMyRec read Get; default;
  end;

{ TMyRecList }

function TMyRecList.Add(Value: PMyRec): Integer;
begin
  Result := inherited Add(Value);
end;

destructor TMyRecList.Destroy;
var
  i: Integer;
begin
  for i := 0 to Count - 1 do
    FreeMem(Items[i]);
  inherited;
end;

function TMyRecList.Get(Index: Integer): PMyRec;
begin
  Result := PMyRec(inherited Get(Index));
end;

var
  MyRecList: TMyRecList;
  MyRec: PMyRec;
  tmp: Integer;
begin
  MyRecList := TMyRecList.Create;
  for tmp := 0 to 9 do
  begin
    GetMem(MyRec, SizeOf(TMyRec));
    MyRec.Value := tmp;
    MyRec.AByte := Byte(tmp);
    MyRecList.Add(MyRec);
  end;

  for tmp := 0 to MyRecList.Count - 1 do
    Writeln('Value: ', MyRecList[tmp].Value, ' AByte: ', MyRecList[tmp].AByte);
  WriteLn('  Press Enter to free the list');
  ReadLn;
  MyRecList.Free;
end.

これにより、いくつかのことがなくなります。

  • メモリの解放を処理します。
  • それを使用するためにすべてを型キャストする必要はありません。

レミーとウォーレンが言ったように、新しいレコードを追加するときにメモリを割り当てる必要があるため、もう少し作業が必要です。

27
Ken White

最初に、クラシックTListをレコードと組み合わせる場合、次のことが必要になります。

  1. スタックではなく、ヒープにレコードを割り当てます。 Remyと同じようにGetMemを使用します。
  2. レコードのアドレスを取得し、TListに追加します。
  3. リストからアイテムを削除して使用するときは、逆参照します。
  4. その後、解放してクリーンアップすることを忘れないでください。

リストとレコードを組み合わせるには、非常に多くの「ポインターとヒープの管理」作業が必要になるため、このような手法は専門家の能力の範囲内でしか実行できません。

あなたが求めているものに代わるものは、まだ「TList」と呼ばれるものを使用しています。これには、TListのすべての利点がありますが、基本的に多くの全体を行う必要があるレコード型でgenerics.collectionsスタイルTListを使用することが含まれますレコードをコピーしてデータを取得します。

Delphiの最も慣用的な方法は、次のいずれかです。

  1. レコードの代わりに、クラスタイプでTListまたはTObjectListを使用します。通常、この場合、TListまたはTObjectListのいずれかをサブクラス化します。

  2. レコード型の動的配列を使用しますが、配列型を並べ替えるのは難しく、実行時に配列型を拡張するのはTListほど高速ではないことに注意してください。

  3. クラスでgenerics.Collections TListを使用します。これにより、異なるクラスのリストを使用するたびにTListまたはTObjectListをサブクラス化する必要がなくなります。

動的配列を示すコードサンプル:

 TMyRec = record
    ///
 end;

 TMyRecArray = array of TMyRec;

 procedure Demo;
 var
    myRecArray:TMyRecArray;
 begin
    SetLength(myRecArray,10);
 end;

次に、TListがレコードタイプで使用するのが簡単でない理由に関する背景情報をいくつか示します。

TListは、「TMyClass = class .... end;」型の変数「TMyClass」の変数であるため、Class型での使用により適しています。 TListが保持するものであるポインタ値として簡単に「参照」できます。

Record型の変数はDelphiの値型ですが、クラス値は暗黙的に参照値です。参照値はステルスポインターと考えることができます。内容を取得するためにそれらを逆参照する必要はありませんが、TListに追加する場合、実際にはコピーを作成したり新しいメモリを割り当てたりするのではなく、TListにポインタを追加するだけです。

Remyの答えは、あなたがどうするかを文字通りあなたに示しています正確にあなたが望むこと、そして私はあなたが尋ねていることの詳細についてあなたに警告し、あなたが考慮することを提案したいという理由だけで私の答えを書いています選択肢も。

18
Warren P

TDynArray wrapper をご覧ください。 Delphi 6からXEまで動作するオープンソースユニットで定義されています。

TDynArrayを使用すると、TListのようなプロパティとメソッドを使用して、動的配列(TIntegerDynArray = array of integerTRecordDynArray = array of TMyRecordなど)にアクセスできます。 Count, Add, Insert, Delete, Clear, IndexOf, Find, SortおよびLoadFromStream, SaveToStream, LoadFromSaveToなどのいくつかの新しいメソッドは、文字列やレコードを含む動的配列の高速バイナリシリアル化を可能にします-CreateOrderedIndexメソッドも利用可能です動的配列の内容に従って個別のインデックスを作成します。必要に応じて、配列の内容をJSONにシリアル化することもできます。 Slice, ReverseまたはCopyメソッドも使用できます。

レコードの動的配列、さらにはレコード内のレコードを処理し、文字列または他の動的配列を内部に格納します。

外部のCount変数を使用すると、参照される動的配列への要素の追加を大幅に高速化できます。

type
  TPerson = packed record
    sCountry: string;
    sFullName: string;
    sAddress: string;
    sCity: string;
    sEmployer: string;
  end;
  TPersons = array of TPerson;
var
  MyPeople: TPersons;

(...)
procedure SavePeopleToStream(Stream: TMemoryStream);
var aPeople: TPerson;
    aDynArray: TDynArray;
begin
  aDynArray.Init(TypeInfo(TPersons),MyPeople);
  aPeople.sCountry := 'France';
  aPeople.sEmployer := 'Republique';
  aDynArray.Add(aPeople);
  aDynArray.SaveToStream(Stream);
end; // no try..finally Free needed here

動的配列コンテンツの内部ハッシュを許可するTDynArrayHashedクラスもあります。非常に高速で、あらゆる種類のデータをハッシュできます(文字列には標準のハッシュがありますが、独自のハッシュを提供できます-ハッシュ関数もカスタマイズできます)。

TDynArrayTDynArrayHashedは、既存の動的配列変数の単なるラッパーであることに注意してください。したがって、必要に応じてTDynArrayラッパーを初期化して、ネイティブのDelphi動的配列により効率的にアクセスできます。

11
Arnaud Bouchez

そのためにTListを使用できます。例:

type
  pRec = ^sRec;
  sRec = record
    Value: Integer;
    ...
  end;

var
  List: TList;
  Rec: pRec;
  I: Integer;
begin
  List := TList.Create;
  try
    for I := 1 to 5 do begin
      GetMem(Rec);
      try
        Rec^.Value := ...;
        ...
        List.Add(Rec);
      except
        FreeMem(Rec);
        raise;
      end;
    end;
    ...
    for I := 0 to List.Count-1 do
    begin
      Rec := pRec(List[I]);
      ...
    end;
    ...
    for I := 0 to List.Count-1 do
      FreeMem(pRec(List[I]));
    List.Clear;
  finally
    List.Free;
  end;
end;
4
Remy Lebeau

それはすべて、保存するデータのタイプによって異なります。

TCollectionおよびTCollectionItemの使用を検討することもできます。

これは、作業ユニットからの(edited)コードです。このコードでは、TCollectionを使用して、フォルダーからレポート定義のリストを読み取りました。各レポートは、ファイル名とともに保存する必要がある一種のテンプレートとSQLステートメントで構成されていました。

それは編集され、独自のユニット(TedlFolderRtnsは内部リストにファイルを読み込みますが、名前を1つだけにします)を使用するため、この例は十分に簡単で便利です。いくつかをすべて置き換えると、必要に応じて適応できます。

ヘルプでTCollectionを検索すると、多くのことができます。また、コード処理がクラスのような構造にうまくまとめられます。

  unit cReports;
  interface
  uses
     SysUtils, Classes, XMLDoc, XMLIntf, Variants,
     // dlib - Edelcom
     eIntList, eProgSettings,eFolder ;
  type

     TReportDefItem = class(TCollectionItem)
     private
        fSql: string;
        fSkeleton: string;
        fFileName: string;
        procedure Load;
        procedure SetFileName(const Value: string);
     public
        constructor Create(Collection:TCollection); override;
        destructor Destroy ; override;

        property FileName: string read fFileName write SetFileName;
        property Sql : string read fSql write fSql;
        property Skeleton : string read fSkeleton write fSkeleton;
     end;

     TReportDefList = class(TCollection)
     private
        function OsReportFolder: string;
        function GetAction(const Index: integer): TReportDefItem;
     public
        constructor Create(ItemClass: TCollectionItemClass);
        destructor Destroy; override;

        procedure LoadList;

        function Add : TReportDefItem;
        property Action [ const Index:integer ]: TReportDefItem read GetAction;
     end;

  implementation

  { TReportDefList }

  constructor TReportDefList.Create(ItemClass: TCollectionItemClass);
  begin
     inherited;
  end;

  destructor TReportDefList.Destroy;
  begin
     inherited;
  end;
  function TReportDefList.Add: TReportDefItem;
  begin
     Result := TReportDefItem( Add() );
  end;

  function TReportDefList.GetAction(const Index: integer): TReportDefItem;
  begin
     if (Index >= 0) and (Index < Count)
     then Result := TReportDefItem( Items[Index] )
     else Result := Nil;
  end;

  procedure TReportDefList.LoadList;
  var Folder : TedlFolderRtns;
      i : integer;
      Itm : TReportDefItem;
  begin
     Folder := TedlFolderRtns.Create;
     try
        Folder.FileList( OsReportFolder,'*.sw.xml', False);
        for i := 0 to Folder.ResultListCount -1 do
        begin
          Itm := Add();
          Itm.FileName := Folder.ResultList[i];
        end;
     finally
        FreeAndNil(Folder);
     end;
  end;

  function TReportDefList.OsReportFolder: string;
  begin
     Result := Application.ExeName + '_RprtDef';
  end;

  { TReportDefItem }

  constructor TReportDefItem.Create(Collection: TCollection);
  begin
     inherited;
     fSql := '';
     fSkeleton := '';
  end;

  destructor TReportDefItem.Destroy;
  begin
    inherited;
  end;

  procedure TReportDefItem.Load;
  var XMLDoc : IXMLDocument;
      TopNode : IXMLNode;
      FileNode : IXmlNode;
      iWebIndex, iRemoteIndex : integer;
      sWebVersion, sRemoteVersion: string;
      sWebFileName: string;
  begin
     if not FileExists(fFileName ) then Exit;

     XMLDoc := TXMLDocument.Create(nil);
     try
        XMLDoc.LoadFromFile( fFileName );
        XMLDoc.Active := True;

        TopNode := XMLDoc.ChildNodes.FindNode('sw-report-def');
        if not Assigned(TopNode) then Exit;

        FileNode := TopNode.ChildNodes.First;
        while Assigned(FileNode) do
        begin
           fSql := VarToStr( FileNode.Attributes['sql'] );
           fSkeleton := VarToStr(  FileNode.Attributes['skeleton'] );
           FileNode := FileNode.NextSibling;
        end;
        XMLDoc.Active := False;
     finally
        XMLDoc := Nil;
     end;
  end;

  procedure TReportDefItem.SetFileName(const Value: string);
  begin
     if fFileName <> Value
     then begin
        fFileName := Value;
        Load;
     end;
  end;
  end.

使用 :

fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();
1
Edelcom

ここで、一般的なレコードのリストで同様の問題が発生しました。次の擬似コードが役立つことを願っています。

type
  PPat = ^TPat;
  TPat = record
    data: integer;
  end;

...
var
    AList: TList<PPat>;

...
procedure TForm1.Button1Click(Sender: TObject);
var
  obj: PPat;
begin
  obj := AList[0];
  obj.data := 1;
  Assert(obj.data = AList[0].data);  // correct
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  obj: PPat;
begin
  AList := TList<PPat>.Create;
  GetMem(obj, SizeOf(TPat));  // not shown but need to FreeMem when items are removed from the list
  obj.data := 2;
  AList.Add(obj);
end;
1
David Moorhouse

ジェネリックが存在しない古いバージョンのDelphiを使用している場合は、TListからの継承を検討し、Notifyメソッドをオーバーライドします。アイテムを追加するとき、メモリを割り当て、追加されたポインタメモリコンテンツをコピーし、リスト内のコンテンツをオーバーライドします。削除するときは、メモリを解放してください。

  TOwnedList = class(TList)
  private
    FPtrSize: integer;
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    constructor Create(const APtrSize: integer);
  end;

  constructor TOwnedList.Create(const APtrSize: integer);
  begin
    inherited Create();
    FPtrSize := APtrSize;
  end;

  procedure TOwnedList.Notify(Ptr: Pointer; Action: TListNotification);
  var
    LPtr: Pointer;
  begin
    inherited;
    if (Action = lnAdded) then begin
      GetMem(LPtr, FPtrSize);
      CopyMemory(LPtr, Ptr, FPtrSize); //May use another copy kind
      List^[IndexOf(Ptr)] := LPtr;
    end else if (Action = lnDeleted) then begin
      FreeMem(Ptr, FPtrSize);
    end;
  end;

使用法:

...
LList := TOwnedList.Create(SizeOf(*YOUR RECORD TYPE HERE*));
LList.Add(*YOU RECORD POINTER HERE*);
...
  • CopyMemory(LPtr、Ptr、FPtrSize)を使用した場所では、別のコピー方法を使用できます。私のリストは、ポインタ参照を持つレコードを保存することを目的としているため、フィールドメモリを管理しません。
0
Lucas Belo