我目前使用记录为函数传递多个结果参数,需要添加更多数据,如下所示:
type
TItemType = (itFile, itRegistry);
TItemDetails = record
Success: Boolean;
ItemType: TItemType;
TotalCount: Integer;
TotalSize: Int64;
List: TStringList;
end;
function DoSomething: TItemDetails;
对于这种特定情况,是否可以/建议在记录中使用TStringList?
我在Embarcadero Developer Network上发现了一个类,它允许声明StringList而不是TStringList,并负责创建和释放列表。这是一个明智的解决方案吗? http://cc.embarcadero.com/Item/25670
另外,如果这确实有效,我是否必须手动释放TStringList?
答案 0 :(得分:4)
是的,无论如何,请注意,如果记录超出范围,则会丢失对该对象的引用(除非您另外添加代码)。
我已经使用了你所引用的StringList示例,这对于让记录管理TStringList的生命周期非常有用。您可以根据您的使用情况进行调整。关键是嵌入式接口,它在对象超出记录范围时释放对象。
您还可以查看Allen Bauer的Nullable record example。我包含了代码,但您也想阅读文章(和评论)。它在Delphi 2009或更新版本中使用Generics,但您可以将其应用于早期版本的Delphi。关键是界面,但他采取了不同的方法。
unit Foo;
interface
uses Generics.Defaults, SysUtils;
type
Nullable<T> = record
private
FValue: T;
FHasValue: IInterface;
function GetValue: T;
function GetHasValue: Boolean;
public
constructor Create(AValue: T);
function GetValueOrDefault: T; overload;
function GetValueOrDefault(Default: T): T; overload;
property HasValue: Boolean read GetHasValue;
property Value: T read GetValue;
class operator NotEqual(ALeft, ARight: Nullable<T>): Boolean;
class operator Equal(ALeft, ARight: Nullable<T>): Boolean;
class operator Implicit(Value: Nullable<T>): T;
class operator Implicit(Value: T): Nullable<T>;
class operator Explicit(Value: Nullable<T>): T;
end;
procedure SetFlagInterface(var Intf: IInterface);
implementation
function NopAddref(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopRelease(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
Result := E_NOINTERFACE;
end;
const
FlagInterfaceVTable: array[0..2] of Pointer =
(
@NopQueryInterface,
@NopAddref,
@NopRelease
);
FlagInterfaceInstance: Pointer = @FlagInterfaceVTable;
procedure SetFlatInterface(var Intf: IInterface);
begin
Intf := IInterface(@FlagInterfaceInstance);
end;
{ Nullable<T> }
constructor Nullable<T>.Create(AValue: T);
begin
FValue := AValue;
SetFlagInterface(FHasValue);
end;
class operator Nullable<T>.Equal(ALeft, ARight: Nullable<T>): Boolean;
var
Comparer: IEqualityComparer<T>;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer<T>.Default;
Result := Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue = ARight.HasValue;
end;
class operator Nullable<T>.Explicit(Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> nil;
end;
function Nullable<T>.GetValue: T;
begin
if not HasValue then
raise Exception.Create('Invalid operation, Nullable type has no value');
Result := FValue;
end;
function Nullable<T>.GetValueOrDefault: T;
begin
if HasValue then
Result := FValue
else
Result := Default(T);
end;
function Nullable<T>.GetValueOrDefault(Default: T): T;
begin
if not HasValue then
Result := Default
else
Result := FValue;
end;
class operator Nullable<T>.Implicit(Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
class operator Nullable<T>.Implicit(Value: T): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
class operator Nullable<T>.NotEqual(const ALeft, ARight: Nullable<T>): Boolean;
var
Comparer: IEqualityComparer<T>;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer<T>.Default;
Result := not Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue <> ARight.HasValue;
end;
end.
答案 1 :(得分:3)
它会起作用,但你必须手动释放它。并且由于记录在超出范围时自动清理,并且没有析构函数,因此确保正确执行操作可能会很麻烦。你最好不要在记录中使用对象。如果您需要包含对象的数据类型,为什么不将其作为对象呢?
答案 2 :(得分:1)
正确记录字符串列表对象的记录的任何解决方案都将以某种方式涉及接口。那么为什么不首先从你的函数返回一个接口呢?向界面添加属性,对于消费代码,它将看起来像记录字段。它允许您以后轻松添加更多“记录字段”,并且可以在返回值的getter中放置任意复杂的代码。
答案 3 :(得分:0)
另一个要注意的问题是,如果使用sizeof来确定记录的内存占用量,它将只包含TStringList指针的大小。如果您尝试将其流式传输,则存储的指针将不可用于以后的实例,因此您必须忽略加载上的指针并使用另一种方法来加载Tstringlist。
例如:
Procedure SaveRecToStream(Rec: TItemDetails ; Stream:tStream);
var
i : integer;
begin
Stream.Write(Rec,SizeOf(Rec)-SizeOf(tSTringList));
Rec.List.saveToStream(Stream);
end;
Procedure LoadRecFromStream(Rec: TItemDetails ; Stream:tStream);
var
i : integer;
begin
FillMemory(@Rec,SizeOf(Rec),0);
i := Stream.Read(rec,SizeOf(Rec)-SizeOf(tStringList));
if i <> SizeOf(Rec)-SizeOf(tStringList) then
Raise Exception.create('Unable to load record');
Rec.List := tStringlist.create;
Rec.List.LoadFromStream(Stream);
end;
这假设每个流只包含一个记录,并且传递给LoadRecFromStream的记录变量不包含实时tStringlist(如果以前使用过,则必须在调用之前释放它或发生泄漏)。
答案 4 :(得分:-2)
为什么不使用
之类的东西type PStringList = ^TStringList;
type TMyFreakyRecord = record
PointerToAStringList : PStringList;
// some more code here
end;
...
var x : TMyFreakyRecord;
stringlist : TStringList;
begin
stringList := TStringlist.create;
stringList.Add('any data you wish');
x.PointertoaStringList := @stringlist;
// some more code here
end;
并访问记录的字符串列表,如
procedure ProcedureThatPasses(AFreakyRecord: TFreakyRecord);
var i : integer;
begin
for i := 0 to AFreakyRecord.PointerToAStringList.count -1 do
// something with AFreakyRecord.PointerToAStringList[i];
end;
为了透明地释放分配的内存,你可以创建一个TList变量,在其中添加在记录中使用的每个TStringList类型的变量,
var frmMain : TfrmMain;
MyJunkList : TList;
...
implementation
...
procedure clearjunk;
var i : integer;
o : TObject;
begin
for i := MyJunkList.count -1 downto 0 do begin
o := MyJunkList[i];
FreeandNil(o);
end;
MyJunkList.clear;
end;
...
initialization
MyJunkList := TList.Create;
finalization
clearjunk;
FreeAndNil(MyJunkList );
end. // end of unit
如果这有帮助,请不要犹豫,访问http://delphigeist.blogspot.com/