【问题标题】:Is it possible/advisable to use a TStringList inside a record?是否可以/建议在记录中使用 TStringList?
【发布时间】:2009-11-21 20:11:07
【问题描述】:

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

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?

【问题讨论】:

    标签: delphi


    【解决方案1】:

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

    我也使用了您所指的 StringList 示例,它非常适合让记录管理 TStringList 的生命周期。您可以根据自己的使用情况进行调整。关键是嵌入式接口,当对象超出记录范围时,它会释放对象。

    你也可以看看Allen BauerNullable record example。我包含了代码,但您也需要阅读这篇文章(和 cmets)。它在 Delphi 2009 或更高版本中使用泛型,但您可以将其调整到早期版本的 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.
    

    【讨论】:

      【解决方案2】:

      它会起作用,但您必须手动释放它。而且由于记录超出范围时会自动清理,并且没有析构函数,因此确保正确执行可能会很麻烦。你最好不要在记录中使用对象。如果你需要一个包含对象的数据类型,为什么不把它也变成一个对象呢?

      【讨论】:

      • 有一些变通方法。记录内的接口将被正确管理。您可以将 TStringList 销毁挂钩到销毁实现此接口的对象。
      • +1 由于这个原因,我经常发现自己将记录更改为类。这就是为什么我倾向于仅将记录用于以下数据结构:a) 非常简单(没有对象成员)和 b) 不太可能扩展(以避免以后的重构)
      • @gabr:关键是您必须恢复解决方法和/或以与所有“正常”记录不同的方式处理这些记录。 变通办法是一种代码味道。
      【解决方案3】:

      对于记录正确生命周期管理字符串列表对象的任何解决方案都将以一种或另一种方式涉及接口。那么为什么不首先从你的函数中返回一个接口呢?向界面添加属性,对于消费代码,它看起来像记录字段。它将允许您稍后轻松添加更多“记录字段”,并且您可以在返回值的 getter 中放置任意复杂的代码。

      【讨论】:

        【解决方案4】:

        另一个需要注意的问题,如果你使用 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(如果以前使用过它,则必须在调用之前释放它,否则会发生泄漏)。

        【讨论】:

          【解决方案5】:

          为什么不使用类似的东西

          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/

          【讨论】:

          • 我不知道这种广告(你所有答案后的博客链接)是否有帮助。只需将链接放在您的个人资料中,不要再为您的答案添加噪音。
          • 虽然编译器足够聪明来处理它,但我更喜欢正确地取消引用指针(AFreakyRecord.PointerToAStringList^.count 而不是 AFreakyRecord.PointerToAStringList.count)
          • 问题是他的尝试是否可行/可取。采取更加复杂和迂回的方法没有任何好处。对象已被指针引用 - 为什么要创建指向指针的指针?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-02
          • 1970-01-01
          • 1970-01-01
          • 2021-11-15
          相关资源
          最近更新 更多