Мне нужно сохранить временный список записей и думал, что TList
будет хорошим способом сделать это? Однако я не уверен, как это сделать с помощью TList
и задавался вопросом, было ли это лучше всего, а также если у кого-нибудь есть примеры того, как это сделать?
Delphi TList записей
Ответ 1
Самый простой способ - создать собственный потомок 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.
Это устраняет несколько вещей:
- Он обрабатывает освобождение памяти.
- Вам не нужно вводить все, чтобы использовать его.
Как сказал Реми и Уоррен, это немного больше, потому что вам нужно выделять память при добавлении новых записей.
Ответ 2
Во-первых, если вы хотите совместить классический TList с записями, вам нужно будет:
- Выделите свои записи в куче, а не в стеке. Используйте GetMem как Remy.
- Возьмите адрес записи и добавьте ее в TList.
- При удалении элемента из списка и его использовании разыщите его:
- Не забудьте освободить и очистить, потом.
Сочетание списков с записями требует так много "указателей и кучи-управления", что такая техника будет только в пределах возможностей эксперта.
Альтернативы тому, что вы просили, чтобы все еще использовать что-то, называемое TList, включают использование TList стиля generics.collections с типами записей, которые будут иметь все преимущества TList, но потребуют от вас в основном сделать много целых копий для получения данных.
Самые идиоматические способы Delphi делать то, что вы просите, либо:
-
используйте TList или TObjectList с типами классов вместо записи. Обычно вы заканчиваете подклассификацию TList или TObjectList в этом случае.
-
Используйте динамический массив типов записей, но имейте в виду, что сложнее сортировать тип массива и расширять тип массива во время выполнения не так быстро, как с TList.
-
Используйте 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", где "type TMyclass= class... end;" может быть легко "упомянут" как значение указателя, которое принадлежит TList.
Переменные типа Record - это значения-типы в Delphi, тогда как значения класса неявно являются значениями ссылки. Вы можете думать о значениях ссылки в качестве указателей стелса. Вам не нужно разыгрывать их, чтобы получить их содержимое, но когда вы добавляете его в TList, вы фактически добавляете указатель на TList, не создаете копию или не выделяете какую-либо новую память.
Ответ Remy показывает вам буквально, как сделать точно, что вы хотите, и я пишу свой ответ только потому, что хочу предупредить вас о деталях вашего запроса и предложить что вы тоже рассматриваете альтернативы.
Ответ 3
Вы можете ознакомиться с нашей оболочкой 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
, которые позволяют быстро выполнять двоичную сериализацию любого динамического массива, даже содержащие строки или записи - метод 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.
Ответ 4
Вы можете использовать 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;
Ответ 5
Мы столкнулись с аналогичной проблемой здесь с общим списком записей. Надеюсь, что следующий код 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;
Ответ 6
Все зависит от типа данных, которые вы хотите сохранить.
Вы можете использовать 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();
Ответ 7
Если вы используете более старую версию 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*));
...
- Обратите внимание, что там, где я использовал CopyMemory (LPtr, Ptr, FPtrSize), вы можете использовать другой способ копирования. Мой список предназначен для хранения записи со ссылками указателя, поэтому он не управляет полями памяти.