Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Kopie eines Item aus der TObjectList (https://www.delphipraxis.net/84800-kopie-eines-item-aus-der-tobjectlist.html)

Kostas 22. Jan 2007 00:10


Kopie eines Item aus der TObjectList
 
Hallo Zusammen,

die Frage vorab: Wenn ich aus einer TObjectList ein item hole etwa so:
Delphi-Quellcode:
CarPosColl := RawTagesauswertung.Items[i];
bekomme ich dan den Pointer des items oder das item selbst?
Ich hätte gerne eine kopie des items.

Ich habe eine Klasse von TObject die Autos verwaltet.
Dann eine Klasse TCarPosCollection von TObjectList die x Autos verwaltet.
Und zuletzt eine Klasse Tagesauswertung ebenfals aus TObjectList
welche x TCarPosCollection verwaltet.

In einer Schleife lese ich aus der TObjectList "Tagesauswertung" ein paar items und
übergebe diese zum verarbeiten in einer zweiten instanz von Klasse.

So wird die übergabe der items gemacht:
Delphi-Quellcode:
CarPosColl := RawTagesauswertung.Items[i];
Wenn ich die Schleife nur ein mal ausführe, funkt alles einwandfrei.

Wenn ich die Schleife ein zweites mal durchführe und dabei vorher
CarPosColl.free aufrufe, bekomme ich eine exception "EAccessViolation".
Ich vermute das ich keine kopie übergebe sonder nur den Pointer des items welches in
meiner Hauptliste steckt. Der Destructor versucht vermutlich das item aus der Haptliste
zu bereinigen.

Habs auch so probiert und bekomme beim free die exception "EPrivilege"
Delphi-Quellcode:
     
CarPosColl := TCarPosCollection.Create;
CarPosColl := RawTagesauswertung.Items[i];

Hat jemand eine Idee?
Hier meine Klassen aus das wesendliche reduziert:
Gruß Kostas


Delphi-Quellcode:
type
  TCarPos = class(TObject)
  private
    FZeitpunkt:TDateTime;
    FCarID:integer;
    Procedure SetZeitpunkt(value :TDateTime);
    function GetZeitpunkt:TDateTime;
    Procedure SetCarID(value :Integer);
    function GetCarID:Integer;
  public
    constructor Create;
    property Zeitpunkt: TDateTime read GetZeitpunkt write SetZeitpunkt;
    property CarID: Integer read GetCarID write SetCarID;
  end;


  TCarPosCollection =Class(TObjectList)
  private
    FZustand:Boolean;
    function Get(Index: Integer): TCarPos;
    procedure SetZustand(value :Boolean);
    function GetZustand:Boolean;
  public
    destructor Destroy; override;
    procedure Add(Item: TCarPos);
    property Items[Index: Integer]: TCarPos read Get;
    property Zustand:Boolean read GetZustand write SetZustand;
  end;

  TTagesauswertung =Class(TObjectList)
  private
    function Get(Index: Integer): TCarPosCollection;
  public
    destructor Destroy; override;
    procedure Add(Item: TCarPosCollection);
    property Items[Index: Integer]: TCarPosCollection read Get;
  end;

destructor TCarPosCollection.Destroy;
var
  i: Integer;
begin
  for i := 0 to self.Count - 1 do
    self.Items[i].Free;
  inherited;
end;

procedure TCarPosCollection.Add(Item: TCarPos);
begin
  inherited Add(Item);
end;

function TCarPosCollection.Get(index: integer):TCarPos;
begin
  result := inherited Get(Index);
end;




destructor TTagesauswertung.Destroy;
var
  i: Integer;
begin
  for i := 0 to self.Count - 1 do
    self.Items[i].Free;
  inherited;
end;

procedure TTagesauswertung.Add(Item: TCarPosCollection);
begin
  inherited Add(Item);
end;

function TTagesauswertung.Get(index: integer):TCarPosCollection;
begin
  result := inherited Get(Index);
end;

Muetze1 22. Jan 2007 00:16

Re: Kopie eines Item aus der TObjectList
 
Geb bei der 2. Liste einfach beim Constructor Aufruf ein False mit, dann gibt die TObjectList nicht die Items frei, die in ihr sind. Damit bleiben dann die in der Hauptliste auch weiterhin gültig.

Du bekommst einen Instanzenzeiger. Die TObjectListe verwaltet nur die Instanzenzeiger.

sh17 22. Jan 2007 07:49

Re: Kopie eines Item aus der TObjectList
 
Du überschreibst bei Deiner TCarPosCollection die Destroy-Methode der TObjectList und gibts alle Items frei. Das ist nicht nötig, das macht TObjectList schon.

Hier mal ein Grundgerüst für KLassen, die von TObjectList erben und somit ein Typecasting überflüssig machen:

Ersetze Name durch Deine Klasse (TCarPos)

Delphi-Quellcode:
NameObjectList = class(TObjectList)
protected
  function GetItem(Index: Integer): Name;
  procedure SetItem(Index: Integer; AItem: Name);
public
  function Add(AItem: Name): Integer;
  function Extract(Item: Name): Name;
  function Remove(AItem: Name): Integer;
  function IndexOf(AItem: Name): Integer;
  procedure Insert(Index: Integer; AItem: Name);
  function First: Name;
  function Last: Name;
  property Items[Index: Integer]: Name read GetItem write SetItem; default;
end;


implementation

{ NameObjectList }

function NameObjectList.Add(AItem: Name): Integer;
begin Result := inherited Add(AItem); end;

function NameObjectList.Extract(Item: Name): Name;
begin Result := Name(inherited Extract(Item)); end;

function NameObjectList.First: Name;
begin if Count = 0 then Result := nil else Result := Name(inherited First); end;

function NameObjectList.GetItem(Index: Integer): Name;
begin Result := Name(inherited Items[Index]); end;

function NameObjectList.IndexOf(AItem: Name): Integer;
begin Result := inherited IndexOf(AItem); end;

procedure NameObjectList.Insert(Index: Integer; AItem: Name);
begin inherited Insert(Index, AItem); end;

function NameObjectList.Last: Name;
begin if Count = 0 then Result := nil else Result := Name(inherited Last); end;

procedure NameObjectList.SetItem(Index: Integer; AItem: Name);
begin inherited Items[Index] := AItem; end;

function NameObjectList.Remove(AItem: Name): Integer;
begin Result := inherited Remove(AItem);end;

Muetze1 22. Jan 2007 08:33

Re: Kopie eines Item aus der TObjectList
 
Zitat:

Zitat von sh17
Hier mal ein Grundgerüst für KLassen, die von TObjectList erben und somit ein Typecasting überflüssig machen:

Ersetze Name durch Deine Klasse (TCarPos)

Man braucht dafür nur Items[] neu definieren und damit die GetItem/SetItem (oder wie die auch immer genannt werden bei der Implementation), alle anderen brauchen dafür nicht neu implementiert werden.

Kostas 22. Jan 2007 09:01

Re: Kopie eines Item aus der TObjectList
 
Danke Muetze1 und sh17,

das ist interessant. Es wird also wirklich keine Kopie des items erzeugt.

Ich benötige unbedingt eine Kopie der items, weil während dem Verarbeiten ich ein
paar Eigenschaften der items verändere. Diese Änderung darf sich jedoch nicht im Master widerspiegeln.

Ich habe leider noch nicht verstanden warum ich die ObjectList nicht leeren darf.

Nochmals kurz beschrieben was ich eigentlich vor habe.
Es gibt eine ObjectList Instanz "TTagesauswertung" die hält alle Daten vor die aus
einem Select aus der DB befühlt wird. Die nenne ich jetzt mal Master.

Jetzt möchte ich die Daten verarbeiten. Ich erzeuge eine weitere, leere Instanz von
"TTagesauswertung". Die nenne ich mal Slave.
Danach gehe ich alle items von Master durch und hole mir ein paar items zum verarbeiten
in die Slave. Dieser Vorgang wird mehrmals wiederholt. Dabei sollen die Master unverändert bleiben.
Wenn ich also fertig bin, rufe ich Slave.free und Slave := TTagesauswertung.Create; auf.

Ihr meint jetzt, der constructor darf die items nicht freigeben.
Sicherlich habt Ihr Recht. Ich verstehe es nur noch nicht warum.
Die items von Master dürfen sicherlich nicht freigegeben werden, aber die items von Slave müssen doch freigegeben werden oder? Wie bereits erwähnt, wird Slave öfters erzeugt, befüllt und wieder zerstört.


Habt Ihr eine Idee?

sh17 22. Jan 2007 09:12

Re: Kopie eines Item aus der TObjectList
 
TObjectList bietet einen Mechanismus zum Verwalten seiner Items. Ruft man TObjectList.Create(true) auf, gehören die Items dieser Objectlist und sie werden bei ObjectList.clear bzw ObjectList.Free von dieser wieder freigegeben.

Ruft man TObjectList.Create(false) auf, muss man sich selbst um die Freigaben der Items kümmern.

Um eine Kopie eines Items zu bekommen, musst Du dieses Object TCarPos noch z.b. um die funktionen AssignTo und Clone erweitern:

Delphi-Quellcode:

TCarPos
public
  procedure AssignTo(_Dest : TCarPos);
  function Clone : TCarPos;
end;

procedure TCarPos.AssignTo(_Dest : TCarPos);
begin
  _Dest.Zeitpunkt := Zeitpunkt;
  ...
end;

function TCarPos.Clone : TCarPos;
begin
  Result := TCarPos.Create;
  AssignTo(Result);
end;
Dann machst Du einfach

Delphi-Quellcode:

  for i := 0 to master.count-1 do
    slave.add(master[i].clone)
schon hast Du eine Kopie, die Du mit slave.clear wieder löschst.

idealer Weise erweiterst Du Deine Objektlist auch um AssignTo und Clone um sich die Schleife zu sparen

Kostas 22. Jan 2007 11:21

Re: Kopie eines Item aus der TObjectList
 
ich mache mich sofort an die Arbeit.

Tausend Dank und schöne Grüße.

Kostas

Kostas 22. Jan 2007 22:37

Re: Kopie eines Item aus der TObjectList
 
Hallo sH17,

es funkt beste Sahne mit dem Clonen.
Hab auch die constructoren aus beiden TObjectList entfernt.
Ein bisschen unwohl ist mir noch bei dieser Sache, weil ich noch nicht
sattelfest bin in OOP. (Ich habe es bis jetzt umgehen können. ;-))
Wenn ich das free der TObjectList aufrufe werden dann auch alle
geklonte items gelöscht?
Ist es eigentlich Möglich memory leaks zu finden?

Hier noch meine drei Klassen. Wenn es Dir möglich währe nochmals drüber zu schauen.

Besten Dank nochmals sH17 und schöne Grüße.


Delphi-Quellcode:
TCarPos = class(TObject)
  private
    FZeitpunkt:TDateTime;
    FCarID:integer;
    Procedure SetZeitpunkt(value :TDateTime);
    function GetZeitpunkt:TDateTime;
    Procedure SetCarID(value :Integer);
    function GetCarID:Integer;
  public
    constructor Create;
    property Zeitpunkt: TDateTime read GetZeitpunkt write SetZeitpunkt;
    property CarID: Integer read GetCarID write SetCarID;
    procedure AssignTo(var Dest : TCarPos);
    function Clone: TCarPos;
  end;

  TCarPosCollection =Class(TObjectList)
  private
    FZustand:Boolean;
    function Get(Index: Integer): TCarPos;
  public
    procedure Add(Item: TCarPos);
    property Items[Index: Integer]: TCarPos read Get;
    procedure AssignTo(var Dest : TCarPosCollection);
    function Clone: TCarPosCollection;
  end;

  TTagesauswertung =Class(TObjectList)
  private
    function Get(Index: Integer): TCarPosCollection;
  public
    procedure Add(Item: TCarPosCollection);
    property Items[Index: Integer]: TCarPosCollection read Get;
  end;


{ TCarPos }
constructor TCarPos.Create;
begin
  FZeitpunkt:=0.0;
  FCarID:=0;
end;

procedure TCarPos.AssignTo(var Dest: TCarPos);
begin
  Dest.FZeitpunkt := FZeitpunkt;
  Dest.FCarID := FCarID;
end;

function TCarPos.Clone: TCarPos;
begin
  result := TCarPos.Create;
  AssignTo(result);
end;


{ TCarPosCollection }
procedure TCarPosCollection.Add(Item: TCarPos);
begin
  inherited Add(Item);
end;

function TCarPosCollection.Get(index: integer):TCarPos;
begin
  result := inherited Get(Index);
end;

procedure TCarPosCollection.AssignTo(var Dest: TCarPosCollection);
var
  i:integer;
begin
  Dest.FZustand := FZustand;

  {kopiere alle items vom typ TCarPos}
  for i := 0 to count-1 do
    Dest.add(items[i].clone);
end;

function TCarPosCollection.Clone: TCarPosCollection;
begin
  result := TCarPosCollection.Create;
  AssignTo(result);

end;


{ TTagesauswertung }
procedure TTagesauswertung.Add(Item: TCarPosCollection);
begin
  inherited Add(Item);
end;

function TTagesauswertung.Get(index: integer):TCarPosCollection;
begin
  result := inherited Get(Index);
end;

Muetze1 22. Jan 2007 23:23

Re: Kopie eines Item aus der TObjectList
 
Ich sehe soweit keine Probleme. Sollte alles so klappen. Die Add() Funktion ist nicht unbedingt nötig - ansonsten aber auch nicht falsch.

sh17 23. Jan 2007 07:34

Re: Kopie eines Item aus der TObjectList
 
Solange TObjectList.Create(true) aufgerufen wurde, werden bei Free auch die Items freigegeben.

Noch eine Anmerkung zu

Delphi-Quellcode:
procedure TCarPos.AssignTo(var Dest: TCarPos);
begin
  Dest.FZeitpunkt := FZeitpunkt;
  Dest.FCarID := FCarID;
end;
In diesem Fall ist die Verwendung von Dest.FZeitpunkt und Dest.FCarID kein Problem, da die Set-Methoden der Properties Zeitpunkt und CarID auch nix anderes machen. Sollte allerdings in den Set-Methoden weiterer Code stehen, der mit dem Wert vorher noch etwas macht, dann musst Du folgendes aufrufen:

Delphi-Quellcode:
procedure TCarPos.AssignTo(var Dest: TCarPos);
begin
  Dest.Zeitpunkt := Zeitpunkt;
  Dest.CarID := CarID;
end;
Bei

Delphi-Quellcode:
procedure TCarPosCollection.AssignTo(var Dest: TCarPosCollection);
var
  i:integer;
begin
  Dest.FZustand := FZustand;

  {kopiere alle items vom typ TCarPos} 
  for i := 0 to count-1 do
    Dest.add(items[i].clone);
end;
wäre ggf noch ein Dest.Clear zu Beginn sinnvoll.

Kostas 23. Jan 2007 08:14

Re: Kopie eines Item aus der TObjectList
 
Zitat:

Zitat von Muetze1
Ich sehe soweit keine Probleme. Sollte alles so klappen. Die Add() Funktion ist nicht unbedingt nötig - ansonsten aber auch nicht falsch.

Hallo Muetze1,

habe es so belassen um beim add nicht typecasten zu müssen.
Entfernt, ist es dann wie generics bei c#

Gruß Kostas

Sidorion 23. Jan 2007 08:26

Re: Kopie eines Item aus der TObjectList
 
Zitat:

Zitat von sh17
Delphi-Quellcode:
procedure NameObjectList.SetItem(Index: Integer; AItem: Name);
begin inherited Items[Index] := AItem; end;

Grober Schnitzer! Beim Aufruf der geerbten Property (TObjectList.Items) wird die Setter-Methode gesucht. Das wäre TObjectList.SetItem. Da diese aber virtuell ist, wird geschaut, ob sie überschrieben ist. Da dies der Fall ist, wird die geerbte Methode gerufen und das wäre NameOjectList.SetItem und das gibt ne Endlosschleife.

Um zu erzwingen, dass die geerbte Methode gerufen wird musst Du direkt die Methode rufen, also:
Delphi-Quellcode:
procedure NameObjectList.SetItem(Index: Integer; AItem: Name);
begin inherited SetItem(Index,AItem); end;
das gleiche gilt für die getter-Methode.

p.s.: Das ist einer der wenigen Fälle, wo man nicht die Property, sondern die dazugehörigen Getter/Setter aufruft, ansonsten hast Du Recht mit der bevorzugten Verwendung der Property.

Muetze1 23. Jan 2007 08:27

Re: Kopie eines Item aus der TObjectList
 
Zitat:

Zitat von Kostas
habe es so belassen um beim add nicht typecasten zu müssen.

Hä? Wieso typecast? Ich versteh nix. Wenn du Add() nicht veränderst, dann nimmt er Parameter vom Typ TObject. Da deine Klasse von TObject abgeleitet ist, akzeptiert er diese ohne Probleme. Du musst typecasten, wenn dein Add() einen höhren Typ hat. Also in deiner Methode musst du typecasten, nicht umgekehrt...

@Sidorion: Probier bitte deine Behauptung mal aus. Die GetItem() und SetItem() sind privat in der TObjectList und nicht virtual. Damit werden die SetItem() und GetItem() von TObjectList versteckt und sind nicht mehr aufrufbar. Somit kannst du schlecht ein Inherited SetItem() aufrufen. Der hier gepostete Weg ist der richtige. Mit dem Inherited Items[] greift er auf die Items der TObjectList zu und diese nutzen ihre (in der Ableitung versteckten) GetItem() und SetItem() Methoden. Die TObjectList könnte die GetItem() und SetItem() von der Ableitung nur aufrufen, wenn diese überschrieben wurden - und das sind sie nicht bzw. können sie nicht.

Probier dein geschriebenes bitter selber aus!

/EDIT: Nachgeschaut: SetItem() und GetItem() sind protected und nicht virtuell. Damit können Sie zwar aufgerufen werden mit dem Inherited, aber die o.g. Schleife kann nicht eintreten. Der Inherited Items[] Zugriff greift auf die GetItem() und SetItem() der TObjectList zu. Diese beiden Methoden werden nur versteckt, nicht aber überschrieben und somit gibt es keine Schleife. Eine Schleife würde es beim Zugriff auf Items[] ohne Inherited geben.

Kostas 23. Jan 2007 08:29

Re: Kopie eines Item aus der TObjectList
 
Zitat:

Zitat von sh17
Solange TObjectList.Create(true) aufgerufen wurde, werden bei Free auch die Items freigegeben.

Das habe ich so gemacht, aber jetzt erst verstanden. :-)


Zitat:

Zitat von sh17
Delphi-Quellcode:
procedure TCarPos.AssignTo(var Dest: TCarPos);
begin
  Dest.FZeitpunkt := FZeitpunkt;
  Dest.FCarID := FCarID;
end;
In diesem Fall ist die Verwendung von Dest.FZeitpunkt und Dest.FCarID kein Problem, da die Set-Methoden der Properties Zeitpunkt und CarID auch nix anderes machen. Sollte allerdings in den Set-Methoden weiterer Code stehen, der mit dem Wert vorher noch etwas macht, dann musst Du folgendes aufrufen:

Delphi-Quellcode:
procedure TCarPos.AssignTo(var Dest: TCarPos);
begin
  Dest.Zeitpunkt := Zeitpunkt;
  Dest.CarID := CarID;
end;

Ah, schön langsam Dämmert, ich dachte nur die Privaten Felder dürfen geclont werden.
Ich werde sofort auf Properties umstellen.

Zitat:

Zitat von sh17

Delphi-Quellcode:
procedure TCarPosCollection.AssignTo(var Dest: TCarPosCollection);
var
  i:integer;
begin
  Dest.FZustand := FZustand;

  {kopiere alle items vom typ TCarPos} 
  for i := 0 to count-1 do
    Dest.add(items[i].clone);
end;
wäre ggf noch ein Dest.Clear zu Beginn sinnvoll.

Dest.Clear habs eingebaut.

Es funkt alles einwandfrei.

Super Leute besten Dank und schöne Grüße.

Kostas

Sidorion 23. Jan 2007 10:34

Re: Kopie eines Item aus der TObjectList
 
@Muetze1: Hmmm. nicht virtuell... dann vergiss meine Behauptung mit der Schleife (in diesem Fall). Jedoch ist die Aussage an sich nicht falsch, da sobald ein Setter/Getter virtuell ist kommt es eben zu diesem Problem (hab ich schon erlebt). Darum stimmt die Aussage 'Inherited SetItem' zu rufen immernoch, weil niemand kann garantieren, dass TObjectList.SetItem für immer statisch ist :wink: Den 'groben Schnitzer' nehme ich also offiziell zurück :mrgreen:

Muetze1 23. Jan 2007 11:15

Re: Kopie eines Item aus der TObjectList
 
Nö, weil wenn GetItem() SetItem virtuell sind, dann würdest du diese immernoch nicht überschreiben. Statt dessen würdest du eine Warnung vom Compiler bekommen (verdeckt virtuelle Methode aus der Basisklasse TObjectList, etc) aber trotzdem würdest du nichts überschreiben. Die alten werden verdeckt und du implementierst ganz neue. Dadurch würde inherited immerncoh die Originalen Methoden von TObjectList aufrufen und nicht die eigenen.
Erst wenn du in deiner Ableitung der Warnung die Grundlage entziehst und die virtuellen Methoden richtig überschreibst mit Override, dann würde dein geschildertes Szenario eintreten. So lange du aber die Warnung stehen lässt oder aber mit ReIntroduce diese unterdrückst, hast du keine Probleme mit dem Inherited SetItem() / GetItem().


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:37 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz