Delphi TList de dossiers

j'ai besoin de stocker une liste temporaire d'enregistrements et je pensais que TList serait un bon moyen de faire ça? Cependant, je ne sais pas comment faire avec un TList et je me demandais si c'est le meilleur et aussi si quelqu'un a des exemples de comment faire cela?

30
demandé sur Kromster 2011-04-27 02:46:48

6 réponses

Le plus simple est de créer votre propre descendant de TList. Voici un exemple rapide d'application de console à démontrer:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
  PMyRec=^TMyRec;
  TMyRec=record
    Value: Integer;
    AByte: Byte;
  end;

  TMyRecList=class(TList)
  private
    function Get(Index: Integer): PMyRec;
  public
    destructor Destroy; override;
    function Add(Value: PMyRec): Integer;
    property Items[Index: Integer]: PMyRec read Get; default;
  end;

{ TMyRecList }

function TMyRecList.Add(Value: PMyRec): Integer;
begin
  Result := inherited Add(Value);
end;

destructor TMyRecList.Destroy;
var
  i: Integer;
begin
  for i := 0 to Count - 1 do
    FreeMem(Items[i]);
  inherited;
end;

function TMyRecList.Get(Index: Integer): PMyRec;
begin
  Result := PMyRec(inherited Get(Index));
end;

var
  MyRecList: TMyRecList;
  MyRec: PMyRec;
  tmp: Integer;
begin
  MyRecList := TMyRecList.Create;
  for tmp := 0 to 9 do
  begin
    GetMem(MyRec, SizeOf(TMyRec));
    MyRec.Value := tmp;
    MyRec.AByte := Byte(tmp);
    MyRecList.Add(MyRec);
  end;

  for tmp := 0 to MyRecList.Count - 1 do
    Writeln('Value: ', MyRecList[tmp].Value, ' AByte: ', MyRecList[tmp].AByte);
  WriteLn('  Press Enter to free the list');
  ReadLn;
  MyRecList.Free;
end.

Ceci élimine un couple de choses:

  • il gère la libération de la mémoire.
  • vous n'avez pas à tout taper pour l'utiliser.

comme Remy et Warren l'ont dit, c'est un peu plus de travail parce que vous devez allouer la mémoire quand vous ajoutez de nouveaux enregistrements.

28
répondu Ken White 2011-04-26 23:36:24

tout d'abord, si vous voulez combiner un TList classique avec des enregistrements, vous aurez besoin de:

  1. Allouer vos dossiers sur le tas, pas sur la pile. Utilise GetMem comme Remy l'a fait.
  2. prendre l'adresse du dossier et l'ajouter au TList.
  3. lorsque vous supprimez un élément de la liste, et que vous l'utilisez, déréférez-le:
  4. rappelez-vous de libérer et de nettoyer, après.

combiner des listes avec des enregistrements demande tellement "pointeurs-et-tas-gestion" de travail que cette technique serait seulement dans les capacités d'un expert.

Les Alternatives à ce que vous avez demandé qui utilisent toujours quelque chose appelé "TList", comprennent l'utilisation d'un générique.collections style TList, avec des types de disques, qui auraient tous les avantages de TList, mais vous exigerait essentiellement de faire beaucoup de-record-copies entières pour obtenir des données dans elle.

les façons les plus idiomatiques Delphi de faire ce que vous demandez sont de soit:

  1. utilisez un TList ou un TObjectList avec un type de classe au lieu d'un enregistrement. D'habitude, vous finissez par sous-classer TList ou TObjectList dans ce cas.

  2. Utilisez un tableau dynamique de types D'enregistrements, mais sachez qu'il est plus difficile de trier un type de tableau, et que l'extension d'un type de tableau à l'exécution n'est pas aussi rapide qu'avec un TList.

  3. Utilisez des génériques.Collections TList avec vos classes. Cela vous permet d'éviter sousclassing TList ou TObjectList chaque fois que vous voulez utiliser une liste avec une classe différente.

Un exemple de code montrant des tableaux Dynamiques:

 TMyRec = record
    ///
 end;

 TMyRecArray = array of TMyRec;

 procedure Demo;
 var
    myRecArray:TMyRecArray;
 begin
    SetLength(myRecArray,10);
 end;

maintenant pour quelques informations de base sur pourquoi TList n'est pas facile à utiliser avec les types D'enregistrement:

TList est mieux adapté pour une utilisation avec les types de classe, parce qu'une variable de type 'TMyClass', où 'Type TMyClass = class .... end; 'peut être facilement "appelé" comme une valeur de pointeur, ce qui est ce que TList tient.

Les Variables D'enregistrement de type sont des types de valeurs dans Delphi, alors que les valeurs de classe sont implicitement des valeurs de référence. Vous pouvez considérer les valeurs de référence comme des pointeurs furtifs. Vous n'avez pas à les déréférer pour accéder à leur contenu, mais quand vous l'ajoutez à un TList, vous ajoutez en fait juste un pointeur sur le TList, sans faire de copie ou allouer une nouvelle mémoire.

la réponse de Remy vous montre littéralement comment faire exactement ce que vous voulez, et Je n'écris ma réponse que parce que je veux vous mettre en garde contre les détails de ce que vous demandez et vous suggérer d'envisager des solutions de rechange.

17
répondu Warren P 2011-04-27 13:28:19

vous pouvez jeter un oeil à notre TDynArray wrapper. Il est défini dans une unité Open Source, allant de Delphi 6 à XE.

TDynArray, vous pouvez accéder à un tableau dynamique (comme TIntegerDynArray = array of integer ou TRecordDynArray = array of TMyRecord) à l'aide de TListpropriétés et méthodes, par exemple,Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort et quelques nouvelles méthodes comme LoadFromStream, SaveToStream, LoadFrom et SaveTo qui permettent une sérialisation binaire rapide de n'importe quel tableau dynamique, même contenant des chaînes ou des enregistrements-a CreateOrderedIndex méthode est également disponible pour créer des index selon le contenu du tableau dynamique. Vous pouvez également sérialiser le contenu du tableau dans JSON, si vous le souhaitez. Slice, Reverse ou Copy méthodes sont également disponibles.

il traitera un tableau dynamique d'enregistrements, et même d'enregistrements à l'intérieur d'enregistrements, avec des chaînes ou d'autres tableaux dynamiques à l'intérieur.

Lors de l'utilisation d'un externe Count variable, vous pouvez accélérer beaucoup l'ajout d'éléments dans la visée de tableau dynamique.

type
  TPerson = packed record
    sCountry: string;
    sFullName: string;
    sAddress: string;
    sCity: string;
    sEmployer: string;
  end;
  TPersons = array of TPerson;
var
  MyPeople: TPersons;

(...)
procedure SavePeopleToStream(Stream: TMemoryStream);
var aPeople: TPerson;
    aDynArray: TDynArray;
begin
  aDynArray.Init(TypeInfo(TPersons),MyPeople);
  aPeople.sCountry := 'France';
  aPeople.sEmployer := 'Republique';
  aDynArray.Add(aPeople);
  aDynArray.SaveToStream(Stream);
end; // no try..finally Free needed here

Il y a aussi un TDynArrayHashed classe, qui permettent le hachage interne d'un contenu de tableau dynamique. Il est très rapide et capable de hacher n'importe quel type de données (il y a des hachoirs standards pour les chaînes, mais vous pouvez fournir votre propre - même la fonction de hachage peut être personnalisée).

Notez que TDynArray et TDynArrayHashed ne sont que des enveloppements autour d'une variable dynamic array existante. Vous pouvez donc initialiser un TDynArray wrapper sur need, pour accéder plus efficacement à N'importe quel tableau dynamique Delphi natif.

11
répondu Arnaud Bouchez 2011-04-27 05:40:09

Vous pouvez utiliser TList pour cela, par exemple:

type
  pRec = ^sRec;
  sRec = record
    Value: Integer;
    ...
  end;

var
  List: TList;
  Rec: pRec;
  I: Integer;
begin
  List := TList.Create;
  try
    for I := 1 to 5 do begin
      GetMem(Rec);
      try
        Rec^.Value := ...;
        ...
        List.Add(Rec);
      except
        FreeMem(Rec);
        raise;
      end;
    end;
    ...
    for I := 0 to List.Count-1 do
    begin
      Rec := pRec(List[I]);
      ...
    end;
    ...
    for I := 0 to List.Count-1 do
      FreeMem(pRec(List[I]));
    List.Clear;
  finally
    List.Free;
  end;
end;
4
répondu Remy Lebeau 2011-04-26 23:00:07

nous venons de rencontrer un problème similaire ici avec une liste générique de dossiers. Espérons que le code psuedo suivant vous aidera.

type
  PPat = ^TPat;
  TPat = record
    data: integer;
  end;

...
var
    AList: TList<PPat>;

...
procedure TForm1.Button1Click(Sender: TObject);
var
  obj: PPat;
begin
  obj := AList[0];
  obj.data := 1;
  Assert(obj.data = AList[0].data);  // correct
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  obj: PPat;
begin
  AList := TList<PPat>.Create;
  GetMem(obj, SizeOf(TPat));  // not shown but need to FreeMem when items are removed from the list
  obj.data := 2;
  AList.Add(obj);
end;
1
répondu David Moorhouse 2011-07-28 23:42:54

tout dépend du type de données que vous souhaitez stocker.

vous pourriez envisager d'utiliser TCollection et TCollectionItem.

Ici (modifié) code d'une unité de travail, dans laquelle j'ai utilisé TCollection pour lire une liste de définitions de rapport à partir d'un dossier. Chaque rapport se composait d'une sorte de modèle et D'une instruction SQL qui devait être stockée avec un nom de fichier.

Puisqu'il est édité, et utilise certaines de mes propres unités (TedlFolderRtns lit les fichiers dans une liste interne, pour n'en nommer qu'un), l'exemple est assez simple pour être utile. Avec quelques remplacements, vous pouvez vous adapter à tous vos besoins.

rechercher TCollection dans l'aide, vous pouvez faire beaucoup avec elle. Et il maintient votre manipulation de code bien groupé ensemble dans une structure de classe-comme.

  unit cReports;
  interface
  uses
     SysUtils, Classes, XMLDoc, XMLIntf, Variants,
     // dlib - Edelcom
     eIntList, eProgSettings,eFolder ;
  type

     TReportDefItem = class(TCollectionItem)
     private
        fSql: string;
        fSkeleton: string;
        fFileName: string;
        procedure Load;
        procedure SetFileName(const Value: string);
     public
        constructor Create(Collection:TCollection); override;
        destructor Destroy ; override;

        property FileName: string read fFileName write SetFileName;
        property Sql : string read fSql write fSql;
        property Skeleton : string read fSkeleton write fSkeleton;
     end;

     TReportDefList = class(TCollection)
     private
        function OsReportFolder: string;
        function GetAction(const Index: integer): TReportDefItem;
     public
        constructor Create(ItemClass: TCollectionItemClass);
        destructor Destroy; override;

        procedure LoadList;

        function Add : TReportDefItem;
        property Action [ const Index:integer ]: TReportDefItem read GetAction;
     end;

  implementation

  { TReportDefList }

  constructor TReportDefList.Create(ItemClass: TCollectionItemClass);
  begin
     inherited;
  end;

  destructor TReportDefList.Destroy;
  begin
     inherited;
  end;
  function TReportDefList.Add: TReportDefItem;
  begin
     Result := TReportDefItem( Add() );
  end;

  function TReportDefList.GetAction(const Index: integer): TReportDefItem;
  begin
     if (Index >= 0) and (Index < Count)
     then Result := TReportDefItem( Items[Index] )
     else Result := Nil;
  end;

  procedure TReportDefList.LoadList;
  var Folder : TedlFolderRtns;
      i : integer;
      Itm : TReportDefItem;
  begin
     Folder := TedlFolderRtns.Create;
     try
        Folder.FileList( OsReportFolder,'*.sw.xml', False);
        for i := 0 to Folder.ResultListCount -1 do
        begin
          Itm := Add();
          Itm.FileName := Folder.ResultList[i];
        end;
     finally
        FreeAndNil(Folder);
     end;
  end;

  function TReportDefList.OsReportFolder: string;
  begin
     Result := Application.ExeName + '_RprtDef';
  end;

  { TReportDefItem }

  constructor TReportDefItem.Create(Collection: TCollection);
  begin
     inherited;
     fSql := '';
     fSkeleton := '';
  end;

  destructor TReportDefItem.Destroy;
  begin
    inherited;
  end;

  procedure TReportDefItem.Load;
  var XMLDoc : IXMLDocument;
      TopNode : IXMLNode;
      FileNode : IXmlNode;
      iWebIndex, iRemoteIndex : integer;
      sWebVersion, sRemoteVersion: string;
      sWebFileName: string;
  begin
     if not FileExists(fFileName ) then Exit;

     XMLDoc := TXMLDocument.Create(nil);
     try
        XMLDoc.LoadFromFile( fFileName );
        XMLDoc.Active := True;

        TopNode := XMLDoc.ChildNodes.FindNode('sw-report-def');
        if not Assigned(TopNode) then Exit;

        FileNode := TopNode.ChildNodes.First;
        while Assigned(FileNode) do
        begin
           fSql := VarToStr( FileNode.Attributes['sql'] );
           fSkeleton := VarToStr(  FileNode.Attributes['skeleton'] );
           FileNode := FileNode.NextSibling;
        end;
        XMLDoc.Active := False;
     finally
        XMLDoc := Nil;
     end;
  end;

  procedure TReportDefItem.SetFileName(const Value: string);
  begin
     if fFileName <> Value
     then begin
        fFileName := Value;
        Load;
     end;
  end;
  end.

utiliser comme :

fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();
0
répondu Edelcom 2011-04-27 04:24:45