是否可以/建议在记录中使用TStringList?

时间:2009-11-21 20:11:08

标签: delphi

我目前使用记录为函数传递多个结果参数,需要添加更多数据,如下所示:

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?

5 个答案:

答案 0 :(得分:4)

是的,无论如何,请注意,如果记录超出范围,则会丢失对该对象的引用(除非您另外添加代码)。

我已经使用了你所引用的StringList示例,这对于让记录管理TStringList的生命周期非常有用。您可以根据您的使用情况进行调整。关键是嵌入式接口,它在对象超出记录范围时释放对象。

您还可以查看Allen BauerNullable 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/