レコードの一時的なリストを保存する必要があり、TList
がこれを行うのに良い方法だと考えていましたか?ただし、TList
を使用してこれを行う方法は不明であり、これが最善かどうか、またこれを行う方法の例があるかどうか疑問に思っていましたか?
最も簡単な方法は、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をレコードと組み合わせる場合、次のことが必要になります。
リストとレコードを組み合わせるには、非常に多くの「ポインターとヒープの管理」作業が必要になるため、このような手法は専門家の能力の範囲内でしか実行できません。
あなたが求めているものに代わるものは、まだ「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 = class .... end;」型の変数「TMyClass」の変数であるため、Class型での使用により適しています。 TListが保持するものであるポインタ値として簡単に「参照」できます。
Record型の変数はDelphiの値型ですが、クラス値は暗黙的に参照値です。参照値はステルスポインターと考えることができます。内容を取得するためにそれらを逆参照する必要はありませんが、TListに追加する場合、実際にはコピーを作成したり新しいメモリを割り当てたりするのではなく、TListにポインタを追加するだけです。
Remyの答えは、あなたがどうするかを文字通りあなたに示しています正確にあなたが望むこと、そして私はあなたが尋ねていることの詳細についてあなたに警告し、あなたが考慮することを提案したいという理由だけで私の答えを書いています選択肢も。
TDynArray wrapper をご覧ください。 Delphi 6からXEまで動作するオープンソースユニットで定義されています。
TDynArray
を使用すると、TList
のようなプロパティとメソッドを使用して、動的配列(TIntegerDynArray = array of integer
やTRecordDynArray = array of TMyRecord
など)にアクセスできます。 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動的配列により効率的にアクセスできます。
そのために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;
それはすべて、保存するデータのタイプによって異なります。
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();
ここで、一般的なレコードのリストで同様の問題が発生しました。次の擬似コードが役立つことを願っています。
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;
ジェネリックが存在しない古いバージョンの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*);
...