Delphi TList de registros
record (6)
Acabamos de encontrarnos con un problema similar aquí con una lista genérica de registros. Espero que el siguiente código psuedo ayude.
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;
Necesito almacenar una lista temporal de registros y estaba pensando que un TList
sería una buena forma de hacerlo. Sin embargo, no estoy seguro de cómo hacer esto con un TList
y me preguntaba si esto es lo mejor y si alguien tiene algún ejemplo de cómo hacerlo.
La forma más fácil es crear tu propio descendiente de TList
. Aquí hay una aplicación de consola de muestra rápida para demostrar:
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.
Esto elimina un par de cosas:
- Se encarga de liberar la memoria.
- No tiene que encasillar todo para usarlo.
Como dijeron Remy y Warren, es un poco más de trabajo porque tienes que asignar la memoria cuando agregas nuevos registros.
Primero, si quiere combinar un TList clásico con Registros, necesitará:
- Asigna tus registros en el montón, no en la pila. Usa GetMem como lo hizo Remy.
- Tome la dirección del registro y agréguelo al TList.
- Al eliminar un elemento de la lista y usarlo, desreferenciarlo:
- Recuerde liberar y limpiar, después.
Combinar Listas con Registros requiere tanto trabajo de "punteros y heap-management" que tal técnica solo estaría dentro de las capacidades de un experto.
Las alternativas a lo que ha solicitado que todavía usan algo llamado "TList" incluyen el uso de un estilo genist.collections TList, con tipos de registros, que tendrían todos los beneficios de TList, pero requeriría básicamente hacer un montón de copias de registro para obtener datos en él.
Las formas más idiomáticas de Delphi para hacer lo que pides son:
use un TList o TObjectList con un tipo de clase en lugar de un registro. Normalmente terminas subclasando TList o TObjectList en este caso.
Utilice una matriz dinámica de tipos de registros, pero tenga en cuenta que es más difícil ordenar un tipo de matriz, y que expandir un tipo de matriz en tiempo de ejecución no es tan rápido como lo es con un TList.
Usa genéricos. Colecciones TList con tus clases. Esto le permite evitar la subclasificación de TList o TObjectList cada vez que quiera utilizar una lista con una clase diferente.
Un ejemplo de código que muestra matrices dinámicas:
TMyRec = record
///
end;
TMyRecArray = array of TMyRec;
procedure Demo;
var
myRecArray:TMyRecArray;
begin
SetLength(myRecArray,10);
end;
Ahora para obtener información general sobre por qué TList no es fácil de usar con los tipos de registros:
TList es más adecuado para su uso con tipos de clase, porque una variable de tipo ''TMyClass'', donde ''tipo TMyClass = clase .... fin;'' puede "referirse a" fácilmente como un valor de puntero, que es lo que TList tiene.
Las variables de tipo Record son Value-Types en Delphi, mientras que los valores de clase son implícitamente valores de referencia. Puede pensar en valores de referencia por referencia como punteros sigilosos. No tiene que desreferenciarlos para obtener sus contenidos, pero cuando los agrega a un TList, en realidad solo está agregando un puntero al TList, no haciendo una copia ni asignando ninguna memoria nueva.
La respuesta de Remy te muestra literalmente cómo hacer exactamente lo que quieres, y solo escribo mi respuesta porque quiero advertirte sobre los detalles de lo que estás preguntando, y sugiero que consideres alternativas también.
Puede usar TList para eso, por ejemplo:
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;
Puedes echar un vistazo a nuestra envoltura TDynArray . Se define en una unidad de código abierto, que funciona desde Delphi 6 hasta XE.
Con TDynArray
, puede acceder a cualquier matriz dinámica (como TIntegerDynArray = array of integer
o TRecordDynArray = array of TMyRecord
) usando propiedades y métodos tipo TList
, por ejemplo Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort
y algunos nuevos métodos como LoadFromStream, SaveToStream, LoadFrom
y SaveTo
que permiten la serialización binaria rápida de cualquier matriz dinámica, incluso que contenga cadenas o registros; un método CreateOrderedIndex
también está disponible para crear índices individuales de acuerdo con el contenido de la matriz dinámica. También puede serializar el contenido de la matriz en JSON, si lo desea. Slice, Reverse
métodos Slice, Reverse
o Copy
también están disponibles.
Manejará una matriz dinámica de registros, e incluso registros dentro de registros, con cadenas u otras matrices dinámicas dentro.
Al usar una variable de Count
externa, puede acelerar mucho la adición de elementos en la matriz dinámica referida.
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
También hay una clase TDynArrayHashed
, que permite el hashing interno de un contenido de matriz dinámico. Es muy rápido y es capaz de cifrar cualquier tipo de datos (hay hashers estándar para cadenas, pero puede suministrar los suyos, incluso la función hash se puede personalizar).
Tenga en cuenta que TDynArray
y TDynArrayHashed
son solo envoltorios alrededor de una variable de matriz dinámica existente. Por lo tanto, puede inicializar un wrapper TDynArray
cuando lo necesite, para acceder de manera más eficiente a cualquier matriz dinámica nativa de Delphi.
Todo depende del tipo de datos que desee almacenar.
Puede considerar usar TCollection
y TCollectionItem
.
Aquí está el código ( editado ) de una unidad de trabajo, en el que utilicé TCollection
para leer una lista de definiciones de informes de una carpeta. Cada informe consistía en una especie de plantilla y una declaración SQL que debía almacenarse junto con un nombre de archivo.
Como está editado y usa algunas de mis unidades (TedlFolderRtns lee archivos en una lista interna, por nombrar solo uno), el ejemplo es lo suficientemente simple como para ser útil. Con unos pocos reemplaza todo, puedes adaptarte a tus necesidades.
Busque TCollection en la ayuda, puede hacer mucho con eso. Y mantiene el manejo de su código muy bien agrupado en una estructura de clase.
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.
Usar como :
fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();