Which lists could serve as temporary lists?

扶醉桌前 提交于 2019-12-22 04:40:19

问题


When working with lists of items where the lists just serve as a temporary container - which list types would you recommend me to use?

I

  • don't want to destroy the list manually
  • would like to use a built-in list type (no frameworks, libraries, ...)
  • want generics

Something which would make this possible without causing leaks:

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;

What options do I have? This is about Delphi 2009 but for the sake of knowledge please also mention if there is something new in this regard in 2010+.


回答1:


The standard list classes, like TList, TObjectList, TInterfaceList, etc, do not implement automated lifecycles, so you have to free them manually when you are done using them. If you want a list class that is accessible via an interface, you have to implement that yourself, eg:

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:


An (somehow ugly) workaround for this is to create an 'autodestroy' interface along with the list. It must have the same scope so that when the interface is released, your list is destroyed too.

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);

Your usage example gets more complicated, too.

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;



回答3:


Inspired by Barry Kelly's blog post here you could implement smart pointers for your purpose like this :

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.

Look at getList and Button1click to see its usage.




回答4:


To fully support what you're after the language would need to support 2 things:

  • Garbage collector. That's the only thing that gives you the freedom to USE something without bothering with freeing it. I'd welcome a change in Delphi that gave us even partial support for this.
  • The possibility to define local, initialized variables. Again, I'd really love to see something along those lines.

Meanwhile, the closest you can get is to use Interfaces in place of garbage collection (because interfaces are reference-counted, once they go out of scope they'll be released). As for initialized local variables, you could use a trick similar to what I'm describing here: Declaring block level variables for branches in delphi

And for the sake of fun, here's a Console application that demonstrates the use of "fake" local variables and Interfaces to obtain temporary lists that are readily initialized will be automatically freed:

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.



回答5:


Another option is to implement a generic IEnumerable adapter (as one of the ways to satisfy the for .. in compiler requirement) and rely on reference counting of the interface. I don't know if the following works in Delphi 2009, it seems to work in 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.



回答6:


No, not 'out of the box' in Delphi. I know that you don't need a library but you may be interessed by the principle of TDynArray.




回答7:


In Jedi Code Library, exist the Guard function that already implements what Gabr's code does.



来源:https://stackoverflow.com/questions/8077248/which-lists-could-serve-as-temporary-lists

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!