Delphi TList записей

мне нужно сохранить временный список записей и подумал, что это TList был бы хороший способ сделать это? Однако я не уверен, как это сделать с TList и было интересно, является ли это лучшим, а также есть ли у кого-нибудь примеры того, как это сделать?

6 ответов


самый простой способ-создать своего собственного потомка 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.

это устраняет пару вещей:

  • он обрабатывает освобождение памяти.
  • вам не нужно печатать все, чтобы использовать его.

Как сказали Реми и Уоррен, это немного больше работы, потому что вы должны выделить память при добавлении новых записей.


во-первых, если вы хотите объединить классический TList с записями, вам нужно:

  1. выделите свои записи в куче, а не в стеке. Используйте GetMem, как это сделал Реми.
  2. возьмите адрес записи и добавьте его в TList.
  3. при удалении элемента из списка и его использовании разыменуйте его:
  4. Не забудьте освободить и очистить, после этого.

объединение списков с записями требует так много "указатели и управление кучей" работают, что такая техника будет только в пределах возможностей эксперта.

альтернативы тому, что вы попросили, все еще используют что-то под названием "TList", включают использование дженериков.коллекции стиль TList, с типами записей, которые будут иметь все преимущества TList, но потребует от вас в основном сделать много целых записей копий, чтобы получить данные в него.

самые идиоматические способы Delphi сделать то, что вы просите либо:

  1. используйте TList или TObjectList с типами классов вместо записи. Обычно в этом случае вы заканчиваете подклассом TList или TObjectList.

  2. используйте динамический массив типов записей, но имейте в виду, что сортировать тип массива сложнее и что расширение типа массива во время выполнения не так быстро, как с помощью TList.

  3. использовать дженерики.Коллекции TList с вашими классами. Это позволяет избежать подклассы TList или TObjectList каждый раз, когда вы хотите использовать список с другим классом.

пример кода, показывающий динамические массивы:

 TMyRec = record
    ///
 end;

 TMyRecArray = array of TMyRec;

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

теперь для некоторой справочной информации о том, почему TList не прост в использовании с типами записей:

TList лучше подходит для использования с типами классов, потому что переменная типа "TMyClass", где " type TMyClass = class .... end; 'можно легко" ссылаться " на значение указателя, которое содержит TList.

переменные типа Record являются типами значений в Delphi, тогда как значения класса неявно являются ссылочными значениями. Вы можете думать о ссылочных значениях как о указателях скрытности. Вам не нужно разыменовывать их, чтобы получить их содержимое, но когда вы добавляете его в TList, вы на самом деле просто добавляете указатель на TList, а не делаете копию или выделяете новую память.

ответ Реми показывает вам буквально, как это сделать ровно то, что вы хотите, и Я пишу свой ответ только потому, что хочу предупредить вас о деталях того, что вы спрашиваете, и предложить вам рассмотреть альтернативы.


вы можете взглянуть на наши TDynArray фантик. Он определен в блоке с открытым исходным кодом, работающем от Delphi 6 до XE.

С TDynArray, вы можете получить доступ к любому динамическому массиву (например,TIntegerDynArray = array of integer или TRecordDynArray = array of TMyRecord) через TList-как свойства и методы, например,Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort и некоторые новые методы, такие как LoadFromStream, SaveToStream, LoadFrom и SaveTo которые позволяют быструю двоичную сериализацию любого динамического массива, даже содержащего строки или записи - a 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 класс, который позволяет внутреннее хеширование содержимого динамического массива. Это очень быстро и может хэшировать любые данные (есть стандартные хэшеры для строк, но вы можете предоставить свои собственные - даже хэш-функцию можно настроить).

отметим, что TDynArray и TDynArrayHashed - это просто обертки вокруг существующей переменной динамического массива. Поэтому вы можете инициализировать TDynArray обертка по необходимости, для более эффективного доступа к любому родному динамическому массиву Delphi.


вы можете использовать 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;

мы только что столкнулись с аналогичной проблемой здесь с общим списком записей. Надеюсь, следующий код psuedo поможет.

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;

все зависит от типа данных, которые вы хотите сохранить.

можно использовать TCollection и TCollectionItem.

здесь (редактировать) код из рабочего блока, в котором я использовал TCollection для чтения списка определений отчетов из папки. Каждый отчет состоял из своего рода шаблона и инструкции SQL, которые должны были храниться вместе с именем файла.

так как он редактируется и использует некоторые из моих собственных единиц (TedlFolderRtns считывает файлы во внутренний список, чтобы назвать только один), пример достаточно прост, чтобы быть полезным. С помощью нескольких замените все, вы можете адаптироваться к любым вашим потребностям.

посмотрите 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();