Saturday, February 19, 2011

extract an object from a TObjectList

I have a TObjectList with OwnsObjects = true. It contains quite a few objects. Now I want to remove the object at index Idx from that list, without freeing it.

Is the Extract method the only option?

ExtractedObject := TheList.Extract(TheList[Idx]);

All other methods seem to free the object. I am looking for something a little bit more efficient, that does not do a linear search every time, since I already know the index of the object. Something like an overloaded ...

ExtractedObject := TheList.Extract(Idx);

... which does not exist.

From stackoverflow
  • I don't use Delphi/C++Builder some time ago, but as far as I can renmember thats the only way. My suggestion is to use a TList instead, and manually delete the objects when required.

  • Why not just set OwnsObjects to false, do your removal, then set it to true again?

    Thomas Mueller : too easy ;-) Thanks
  • If you look at the code for delete, it's the notify method which causes the freeing to happen.

    This should work :

      TMyObjectList = Class(TObjectList)
      private
        fNotify: Boolean;
        { Private declarations }
        procedure EnableNotification;
        procedure DisableNotification;
      protected
        procedure Notify(Ptr: Pointer; Action: TListNotification); override;
      public
        constructor Create(AOwnsObjects: Boolean);overload;
        constructor Create; overload;
        function Extract(const idx : Integer) : TObject;
      end;
    
    
    constructor TMyObjectList.Create(AOwnsObjects: Boolean);
    begin
      inherited Create(AOwnsObjects);
      fNotify := True;
    end;
    
    constructor TMyObjectList.Create;
    begin
      inherited Create;
      fNotify := True;
    end;
    
    procedure TMyObjectList.DisableNotification;
    begin
      fnotify := False;
    end;
    
    procedure TMyObjectList.EnableNotification;
    begin
      fNotify := True;
    end;
    
    function TMyObjectList.Extract(const idx: Integer) : TObject;
    begin
      Result := Items[idx];
      DisableNotification;
      try
        Delete(idx);
      finally
        EnableNotification;
      end;
    end;
    
    procedure TMyObjectList.Notify(Ptr: Pointer; Action: TListNotification);
    begin
     if fNotify then
       inherited;
    end;
    
  • This is where class helpers can be usefull

    TObjectListHelper = class helper for TObjectList
      function ExtractByIndex(const AIndex: Integer): TObject;
    end;
    
    function TObjectListHelper.ExtractByIndex(const AIndex: Integer): TObject;
    begin
      Result := Items[AIndex];
     if Result<>nil then
       Extract(Result);
    end;
    

    You can now use:

    MyObjList.ExtractByIndex(MyIndex);
    
    Thomas Mueller : Unfortunately this will still do a linear search in the original extract function, even though I already passed the object's index to the new extract function. But I guess combining this with OwnsObjects=False/True should do the trick.
  • The proposed helperclass (by Gamecat) will result in the same lookup that Thomas would like to get rid of.

    If you take a look at the source, you can see what Extract() really does, and then use the same approach.

    I will suggest something like tis:

    obj := list[idx];
    list.list^[idx] := nil;  //<- changed from list[idx] := nil;
    list.delete(idx);
    

    This will give you the object, as Extract() does, and then delete it from the list, without any lookups. Now you can put this in a method some where, a helperclass or subclass or wher ever you like.

    Thomas Mueller : Unfortunately this doesn't work either since Assigning NIL to an item automatically frees it.
    Vegar : Oops. I completly overlooked that one, but TList gives you direct access to the linkedlist, through the public property List: PPoinerList. Change list[idx] := nil to list.List^[i] := nil; and the solution should be sound again.
  • Anything wrong with:

    ExtractedObject := TExtractedObject.Create;
    ExtractedObject.Assign(Thelist[Idx]);
    TheList.Delete(idx);

    There is time needed for the create and assign but not for the search of the list. Efficiency depends on the size of the object -v- the size of the list.

    Oliver Giesen : that approach requires the object to descend from TPersistent and to properly implement the Assign method

0 comments:

Post a Comment