哪些列表可以作为临时列表?

7
当处理只作为临时容器的项目列表时,您建议我使用哪些列表类型?
我:
- 不想手动销毁列表 - 想要使用内置的列表类型(没有框架、库等) - 想要泛型
希望能找到一种可以在不造成泄漏的情况下实现这一点的方法。
function GetListWithItems: ISomeList;
begin
  Result := TSomeList.Create;
  // add items to list
end;

var
  Item: TSomeType;
begin
  for Item in GetListWithItems do
  begin
    // do something
  end;
end;

我有哪些选项?这是关于Delphi 2009的问题,但为了增加知识,请您也提到2010年以后是否有新内容。


如果有内置类型,我想使用它。我只是想确认一下是否没有我不知道的东西;) 我觉得很难相信没有这样的类型,因为很长一段时间以来,我一再需要它。现在我决定问一下。如果你们不知道,那么我就把它作为不存在的证明。 - Heinrich Ulbricht
好问题:Delphi确实存在这方面的不足。我在D7中经常使用tStringList/TList,TDictionnary似乎有一些进展。 - philnext
1
@philnext,Delphi中“真正缺乏”的是什么? - Cosmin Prund
@CosminPrund 我猜 "不想手动销毁列表" 部分至少缺失了...我记得在旧的 Codegear 论坛上有一些针对 TStringList 的记录包装器实现。 - jpfollenius
7个回答

6
创建一个“自动销毁”接口以及列表是一个(有点丑陋的)解决方法。它必须具有相同的作用域,以便在释放接口时,您的列表也被销毁。
type
  IAutoDestroyObject = interface
  end;

  TAutoDestroyObject = class(TInterfacedObject, IAutoDestroyObject)
  strict private
    FValue: TObject;
  public
    constructor Create(obj: TObject);
    destructor  Destroy; override;
  end;

constructor TAutoDestroyObject.Create(obj: TObject);
begin
  inherited Create;
  FValue := obj;
end; 

destructor TAutoDestroyObject.Destroy;
begin
  FreeAndNil(FValue);
  inherited;
end;

function CreateAutoDestroyObject(obj: TObject): IAutoDestroyObject;
begin
  Result := TAutoDestroyObject.Create(obj);
end; 

FList := TObjectList.Create;
FListAutoDestroy := CreateAutoDestroyObject(FList);

您的使用示例也变得更加复杂。

type
  TSomeListWrap = record
    List: TSomeList;
    AutoDestroy: IAutoDestroyObject;
  end;

function GetListWithItems: TSomeListWrap;
begin
  Result.List := TSomeList.Create;
  Result.AutoDestroy := CreateAutoDestroyObject(Result.List);
  // add items to list
end;

var
  Item: TSomeItem;
begin
  for Item in GetListWithItems.List do
  begin
    // do something
  end;
end;

它会起作用吗?在您的示例代码中,由于Result.AutoDestroy不是自由的(它永远不会超出范围),相关的IAutoDestroyObject也不会被释放。 - Arnaud Bouchez
2
@ArnaudBouchez 它运行得非常好。GetListWithItems的结果在for..in循环周围的begin..end的'end'部分超出了作用域。 - gabr
真的吗,Gabr?Delphi会在函数结束之前释放临时变量吗?这对我来说是新鲜事。通常,临时变量会在函数结束时释放,或者如果它们在函数中的其他代码块中被重用(例如,如果有两个函数调用返回TSomeListWrap而不将其存储在变量中)。 - Rob Kennedy
不,实际上临时变量将在函数结束时被释放,但在这种情况下没有函数,只有一个begin...end块,所以我不能更加具体。 - gabr

5
受Barry Kelly博客文章的启发,您可以按照以下方式实现智能指针以满足您的需求: 这里
unit Unit80;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Generics.Collections;

type
  TMyList =class( TList<Integer>)
  public
    destructor Destroy; override;
  end;

  TLifetimeWatcher = class(TInterfacedObject)
  private
    FWhenDone: TProc;
  public
    constructor Create(const AWhenDone: TProc);
    destructor Destroy; override;
  end;

  TSmartPointer<T: class> = record
  strict private
    FValue: T;
    FLifetime: IInterface;
  public
    constructor Create(const AValue: T); overload;
    class operator Implicit(const AValue: T): TSmartPointer<T>;
    property Value: T read FValue;
  end;

  TForm80 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    function getList : TSmartPointer<TMyList>;
    { Public declarations }
  end;


var
  Form80: TForm80;

implementation


{$R *.dfm}



{ TLifetimeWatcher }

constructor TLifetimeWatcher.Create(const AWhenDone: TProc);
begin
  FWhenDone := AWhenDone;
end;

destructor TLifetimeWatcher.Destroy;
begin
  if Assigned(FWhenDone) then
    FWhenDone;
  inherited;
end;

{ TSmartPointer<T> }

constructor TSmartPointer<T>.Create(const AValue: T);
begin
  FValue := AValue;
  FLifetime := TLifetimeWatcher.Create(procedure
  begin
    AValue.Free;
  end);
end;

class operator TSmartPointer<T>.Implicit(const AValue: T): TSmartPointer<T>;
begin
  Result := TSmartPointer<T>.Create(AValue);
end;

procedure TForm80.Button1Click(Sender: TObject);
 var i: Integer;
begin
  for I in getList.Value do
   Memo1.Lines.Add(IntToStr(i));

end;

{ TMyList }

destructor TMyList.Destroy;
begin
  ShowMessage('Kaputt');
  inherited;
end;

function TForm80.getList: TSmartPointer<TMyList>;
var
  x: TSmartPointer<TMyList>;
begin
  x := TMyList.Create;
  Result := x;
  with Result.Value do
  begin
    Add(1);
    Add(2);
    Add(3);
  end;
end;

end.

查看getList和Button1click以了解其用法。


@TOndrej,感谢应该归功于Barry Kelly。我只是稍微改编了他的例子,以更好地满足OP的需求。 - iamjoosy
明白了,感谢你提供的链接和代码。我记得曾经简单地看过这篇文章,后来完全忘记了它。(我已经修复了链接,直接指向该文章。) - Ondrej Kelle

4
为了完全支持您所需的内容,语言需要支持两个方面:
  • 垃圾回收器。这是唯一可以让您自由使用某些内容而无需费心释放它的东西。我欢迎 Delphi 改变并提供部分支持。
  • 定义本地初始化变量的可能性。同样,我真的很希望看到类似这样的东西。

同时,您可以使用接口代替垃圾回收(因为接口是引用计数的,一旦超出作用域,它们将被释放)。至于初始化的本地变量,您可以使用类似于我在 Delphi 分支中声明块级变量 中描述的技巧。

为了好玩,这里有一个控制台应用程序,演示了使用“虚假”本地变量和接口来获取临时列表,这些列表已经初始化并将自动释放:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections;

type

  ITemporaryLocalVar<T:constructor> = interface
    function GetL:T;
    property L:T read GetL;
  end;

  TTemporaryLocalVar<T:constructor> = class(TInterfacedObject, ITemporaryLocalVar<T>)
  public
    FL: T;

    constructor Create;
    destructor Destroy;override;

    function GetL:T;
  end;

  TTempUse = class
  public
    class function L<T:constructor>: ITemporaryLocalVar<T>;
  end;

{ TTemporaryLocalVar<T> }

constructor TTemporaryLocalVar<T>.Create;
begin
  FL := T.Create;
end;

destructor TTemporaryLocalVar<T>.Destroy;
begin
  TObject(FL).Free;
  inherited;
end;

function TTemporaryLocalVar<T>.GetL: T;
begin
  Result := FL;
end;

{ TTempUse }

class function TTempUse.L<T>: ITemporaryLocalVar<T>;
begin
  Result := TTemporaryLocalVar<T>.Create;
end;

var i:Integer;
begin
  try
    with TTempUse.L<TList<Integer>> do
    begin
      L.Add(1);
      L.Add(2);
      L.Add(3);
      for i in L do
        WriteLn(i);
    end;
    ReadLn;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

@gabr,它也适用于任何具有“Create”构造函数的内容,不仅限于列表。 - Cosmin Prund
可能就是这样了。但不幸的是,它在D2009中无法编译 :( TObject(FL).Free;: "[DCC Error] E2089 Invalid typecast" 我正在尝试一些指针魔法来使其工作... - Heinrich Ulbricht
显然,Delphi 2010变得更加智能了;由于“T”被限制为“constructor”类型,它显然是一个“TObject”后代。很高兴你找到了一个在D2009上也适用的解决方法! - Cosmin Prund

3

标准的列表类,如TListTObjectListTInterfaceList等,并没有实现自动生命周期,因此在使用完后必须手动释放它们。如果你想要一个可通过接口访问的列表类,你必须自己实现,例如:

type
  IListIntf = interface
    ...
  end;

  TListImpl = class(TInterfacedObject, IListIntf)
  private
    FList: TList;
    ...
  public
    constructor Create; override;
    destructor Destroy; override;
    ...
  end;

constructor TListImpl.Create;
begin
  inherited;
  FList := TList.Create;
end;

destructor TListImpl.Destroy;
begin
  FList.Free;
  inherited;
end;

function GetListWithItems: IListIntf;
begin
  Result := TListImpl.Create;
  // add items to list
end;   

2
“自己实现”不幸地成为了解决每个问题的终极方案。我曾希望在RTL中能找到一些有用的东西,但还是感谢你的回答! - Heinrich Ulbricht
我曾认为通用的TObjectList<TMyclass>总是拥有其包含的对象。你是在谈论LIST自己释放,还是仅释放其拥有的对象? - Warren P

2

另一个选择是实现一个通用的 IEnumerable 适配器(作为满足编译器要求的 for .. in 的方式之一),并依赖接口的引用计数。我不知道以下内容是否适用于 Delphi 2009,它似乎适用于 Delphi XE:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes,
  Generics.Collections;

type
  // IEnumerator adapter for TEnumerator
  TInterfacedEnumerator<T> = class(TInterfacedObject, IEnumerator<T>)
  private
    FEnumerator: TEnumerator<T>;
  public
    constructor Create(AEnumerator: TEnumerator<T>);
    destructor Destroy; override;
    function IEnumerator<T>.GetCurrent = GetCurrent2;
    { IEnumerator }
    function GetCurrent: TObject;
    function MoveNext: Boolean;
    procedure Reset;
    { IEnumerator<T> }
    function GetCurrent2: T;
  end;

  // procedure used to fill the list    
  TListInitProc<T> = reference to procedure(List: TList<T>);

  // IEnumerable adapter for TEnumerable
  TInterfacedEnumerable<T> = class(TInterfacedObject, IEnumerable<T>)
  private
    FEnumerable: TEnumerable<T>;
  public
    constructor Create(AEnumerable: TEnumerable<T>);
    destructor Destroy; override;
    class function Construct(InitProc: TListInitProc<T>): IEnumerable<T>;
    function IEnumerable<T>.GetEnumerator = GetEnumerator2;
    { IEnumerable }
    function GetEnumerator: IEnumerator; overload;
    { IEnumerable<T> }
    function GetEnumerator2: IEnumerator<T>; overload;
  end;

{ TInterfacedEnumerator<T> }

constructor TInterfacedEnumerator<T>.Create(AEnumerator: TEnumerator<T>);
begin
  inherited Create;
  FEnumerator := AEnumerator;
end;

destructor TInterfacedEnumerator<T>.Destroy;
begin
  FEnumerator.Free;
  inherited Destroy;
end;

function TInterfacedEnumerator<T>.GetCurrent: TObject;
begin
  Result := TObject(GetCurrent2);
end;

function TInterfacedEnumerator<T>.GetCurrent2: T;
begin
  Result := FEnumerator.Current;
end;

function TInterfacedEnumerator<T>.MoveNext: Boolean;
begin
  Result := FEnumerator.MoveNext;
end;

procedure TInterfacedEnumerator<T>.Reset;
begin
  // ?
end;

{ TInterfacedEnumerable<T> }

class function TInterfacedEnumerable<T>.Construct(InitProc: TListInitProc<T>): IEnumerable<T>;
var
  List: TList<T>;
begin
  List := TList<T>.Create;
  try
    if Assigned(InitProc) then
      InitProc(List);
    Result := Create(List);
  except
    List.Free;
    raise;
  end;
end;

constructor TInterfacedEnumerable<T>.Create(AEnumerable: TEnumerable<T>);
begin
  inherited Create;
  FEnumerable := AEnumerable;
end;

destructor TInterfacedEnumerable<T>.Destroy;
begin
  FEnumerable.Free;
  inherited Destroy;
end;

function TInterfacedEnumerable<T>.GetEnumerator: IEnumerator;
begin
  Result := GetEnumerator2;
end;

function TInterfacedEnumerable<T>.GetEnumerator2: IEnumerator<T>;
begin
  Result := TInterfacedEnumerator<T>.Create(FEnumerable.GetEnumerator);
end;

type
  TSomeType = record
    X, Y: Integer;
  end;

function GetList(InitProc: TListInitProc<TSomeType>): IEnumerable<TSomeType>;
begin
  Result := TInterfacedEnumerable<TSomeType>.Construct(InitProc);
end;

procedure MyInitList(List: TList<TSomeType>);
var
  NewItem: TSomeType;
  I: Integer;
begin
  for I := 0 to 9 do
  begin
    NewItem.X := I;
    NewItem.Y := 9 - I;
    List.Add(NewItem);
  end;
end;

procedure Main;
var
  Item: TSomeType;
begin
  for Item in GetList(MyInitList) do // you could also use an anonymous procedure here
    Writeln(Format('X = %d, Y = %d', [Item.X, Item.Y]));
  Readln;
end;

begin
  try
    ReportMemoryLeaksOnShutdown := True;
    Main;
  except
    on E: Exception do
    begin
      ExitCode := 1;
      Writeln(Format('[%s] %s', [E.ClassName, E.Message]));
    end;
  end;
end.

+1 有趣...即使我不确定这是否值得付出努力。我认为在另一个函数或匿名方法中进行列表初始化会使其可读性大大降低。 - jpfollenius
@Smasher 谢谢!不需要单独的初始化方法;你可以重写GetList以创建并填充列表。 - Ondrej Kelle

1
不,Delphi中没有“开箱即用”。 我知道你不需要一个库,但你可能会对TDynArray的原理感兴趣。

按设计,动态数组将自动销毁,并且TDynArray / TDynArrayHashed包装器将为您的代码提供所有TList类似的方法(以及更多功能,如哈希和序列化)。但这不是对象列表,而是记录(因此它不处理继承等内容)。但是从Delphi 5到XE2都可以使用。这可用于您的目的,但它不是“泛型”。 - Arnaud Bouchez
请参考 https://dev59.com/nG035IYBdhLWcg3wPdgS 了解如何使用此功能。 - Arnaud Bouchez

1
在Jedi Code Library中,存在Guard函数,它已经实现了Gabr代码的功能。

我经常对JCL/JVCL感到惊讶,因为它们包含了很多东西!不幸的是,文档太差了,以至于很难找到答案。 - iamjoosy
事实上,在这里我们很少使用外部库,因此在了解到另一个SO问题之前,我已经自己实现了它。 - Fabricio Araujo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接