web-dev-qa-db-fra.com

Delphi Liste des records

J'ai besoin de stocker une liste temporaire de documents et je pensais qu'une TList serait un bon moyen de le faire? Cependant, je ne suis pas sûr de savoir comment faire cela avec un TList et je me demandais si c'était le meilleur et aussi si quelqu'un avait des exemples montrant comment faire cela.

30
colin

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

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.

Cela élimine un certain nombre de choses:

  • Il gère la libération de la mémoire.
  • Vous n'avez pas besoin de tout transtyper pour l'utiliser.

Comme Remy et Warren l’ont dit, c’est un peu plus de travail car il faut allouer de la mémoire lorsque vous ajoutez de nouveaux enregistrements.

28
Ken White

Tout d'abord, si vous souhaitez combiner une TList classique avec Records, vous devez:

  1. Allouez vos enregistrements sur le tas, pas sur la pile. Utilisez GetMem comme Rémy l’a fait. 
  2. Prenez l'adresse de l'enregistrement et l'ajoutez à la liste de lecture.
  3. Lorsque vous supprimez un élément de la liste et que vous l’utilisez, déréférencez-le:
  4. N'oubliez pas de libérer et de nettoyer, après.

Combiner des listes avec des enregistrements nécessite un travail de "gestion du pointeur et du tas" tellement important qu'une telle technique ne serait à la portée d'un expert.

Les solutions de rechange à ce que vous avez demandé utilisent toujours quelque chose appelé "TList", y compris l'utilisation d'un style TList de style générique.Collections, avec des types Record, qui présenterait tous les avantages de TList, mais vous obligerait à faire beaucoup des copies d’enregistrement pour y insérer des données.

Les manières les plus idiomatiques de Delphi de faire ce que vous demandez sont les suivantes:

  1. utilisez un TList ou TObjectList avec un types de classe au lieu d'un enregistrement. Généralement, vous finissez par sous-classer TList ou TObjectList dans ce cas.

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

  3. Utilisez generics.Collections TList avec vos classes. Cela vous permet d'éviter de sous-classer TList ou TObjectList chaque fois que vous souhaitez utiliser une liste avec une classe différente.

Un exemple de code montrant les 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 les raisons pour lesquelles TList n’est pas facile à utiliser avec les types d’enregistrement:

TList convient mieux aux types de classe, car une variable de type 'TMyClass', où 'type TMyClass = class .... end;' peut être facilement "désigné" comme une valeur de pointeur, ce qui est ce que TList tient. 

Les variables de type Record sont des types de valeur dans Delphi, alors que les valeurs de classe sont implicitement des valeurs par référence. Vous pouvez considérer les valeurs par référence comme des indicateurs furtifs. Vous n'avez pas besoin de les déréférencer pour obtenir leur contenu, mais lorsque vous l'ajoutez à une liste TList, vous ajoutez en fait un pointeur à la liste TList, sans en faire une copie ni en allouant 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 avertir des détails de ce que vous demandez et vous suggérer d’envisager des solutions de rechange. 

18
Warren P

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

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

Il gérera un tableau dynamique d'enregistrements, et même des enregistrements au sein d'enregistrements, avec des chaînes ou d'autres tableaux dynamiques à l'intérieur.

Lorsque vous utilisez une variable Count externe, vous pouvez accélérer considérablement l'ajout d'éléments dans le tableau dynamique référencé.

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 existe également une classe TDynArrayHashed, qui permet 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 existe des hachages standard pour les chaînes, mais vous pouvez en fournir vous-même - même la fonction de hachage peut être personnalisée).

Notez que TDynArray et TDynArrayHashed sont simplement des enveloppeurs autour d'une variable de tableau dynamique existante. Vous pouvez donc initialiser un wrapper TDynArray sur besoin pour accéder plus efficacement à tout tableau dynamique Delphi natif.

11
Arnaud Bouchez

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
Remy Lebeau

Nous venons de rencontrer un problème similaire avec une liste générique d'enregistrements. Espérons que le code suivant psuedo aide.

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
David Moorhouse

Si vous utilisez une version antérieure de Delphi où les génériques ne sont pas présents, envisagez d'hériter de TList et de remplacer la méthode Notify. Lors de l'ajout d'un élément, allouez de la mémoire, copiez le contenu de la mémoire du pointeur ajouté et remplacez le contenu de la liste. Lors du retrait, libérez simplement de la mémoire. 

  TOwnedList = class(TList)
  private
    FPtrSize: integer;
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    constructor Create(const APtrSize: integer);
  end;

  constructor TOwnedList.Create(const APtrSize: integer);
  begin
    inherited Create();
    FPtrSize := APtrSize;
  end;

  procedure TOwnedList.Notify(Ptr: Pointer; Action: TListNotification);
  var
    LPtr: Pointer;
  begin
    inherited;
    if (Action = lnAdded) then begin
      GetMem(LPtr, FPtrSize);
      CopyMemory(LPtr, Ptr, FPtrSize); //May use another copy kind
      List^[IndexOf(Ptr)] := LPtr;
    end else if (Action = lnDeleted) then begin
      FreeMem(Ptr, FPtrSize);
    end;
  end;

Usage:

...
LList := TOwnedList.Create(SizeOf(*YOUR RECORD TYPE HERE*));
LList.Add(*YOU RECORD POINTER HERE*);
...
  • Notez que lorsque j'ai utilisé CopyMemory (LPtr, Ptr, FPtrSize), vous pouvez utiliser une autre copie à peu près. Ma liste est destinée à stocker un enregistrement avec des références de pointeur, afin de ne pas gérer sa mémoire de champs.
0
Lucas Belo

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

Vous pouvez envisager d’utiliserTCollectionetTCollectionItem.

Voici le code (edité) 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 consistait en une sorte de modèle et une instruction SQL qui devaient être stockés avec un nom de fichier.

Comme il est édité et utilise certaines de mes propres unités (TedlFolderRtns lit les fichiers dans une liste interne, pour n'en nommer qu'une), cet exemple est assez simple pour être utile. En remplaçant quelques-uns, vous pouvez vous adapter à tous vos besoins.

Recherchez l'aide de TCollection, vous pouvez en faire beaucoup. Et cela permet de garder votre code manipulé dans une structure de type classe.

  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.

Utilisé comme :

fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();
0
Edelcom