4

I have a procedure that needs to insert an array of TObjects into to a list. The list can be of any of the supported types, e.g. TObjectList, TObjectList<T>, TROArray, etc.

The procedure looks like this:

type
  TObjectArray = Array of TObject;

...

procedure TMyClass.DoAssignObjectList(const ObjectArray: TObjectArray;
  const DstList: TObject);
var
  i: Integer;
begin
  if DstList is TObjectList then
  begin
    for i := 0 to pred(TObjectList(DstList).Count) do
      TObjectList(DstList).Add(ObjectArray[i]);
  end else
  if DstList is TObjectList<T> then // Obviously this doesn't work
  begin
    for i := 0 to pred(TObjectList<T>(DstList).Count) do
      TObjectList<T>(DstList).Add(ObjectArray[i]);
  end
  else
  begin
    raise Exception.CreateFmt(StrNoDoAssignORMObject, [DstList.ClassName]);
  end;
end;

How can I check that an object is a TObjectList<T> and then add the elements of an array to it?

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
norgepaul
  • 6,013
  • 4
  • 43
  • 76

2 Answers2

5

You have to use a bit RTTI to get some more information about the generic type.

The following code uses Spring4D which has some methods for that:

uses 
  ...
  Spring.Reflection;

procedure DoAssignObjectList(const ObjectArray: TObjectArray;
  const DstList: TObject);

  function IsGenericTObjectList(const obj: TObject): Boolean;
  var
    t: TRttiType;
  begin
    t := TType.GetType(obj.ClassInfo);
    Result := t.IsGenericType and (t.GetGenericTypeDefinition = 'TObjectList<>');
  end;

begin
  ...
  if IsGenericTObjectList(DstList) then
  begin
    for i := 0 to pred(TObjectList<TObject>(DstList).Count) do
      TObjectList<TObject>(DstList).Add(ObjectArray[i]);
  ...
end;

Additionally to that you can also get information about the generic parameter type of the list to check if the objects you are putting into it are matching the requirements (only works on a generic type of course):

function GetGenericTObjectListParameter(const obj: TObject): TClass;
var
  t: TRttiType;
begin
  t := TType.GetType(obj.ClassInfo);
  Result := t.GetGenericArguments[0].AsInstance.MetaclassType;
end;
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • Massively off topic, but how can `pred(Count)` be considered better than `Count-1`? – David Heffernan Mar 02 '15 at 20:03
  • @DavidHeffernan, old habits from TP, where Succ/Pred perhaps was better optimized than +/- 1. Perhaps, because I'm not sure I remember correctly. – LU RD Mar 02 '15 at 20:24
2

As I was writing this question I figured out a way to do this using RTTI. It should work with any list that has a procedure Add(AObject: TObject).

procedure TransferArrayItems(const Instance: TObject;
  const ObjectArray: TObjectArray);
const
  AddMethodName = 'Add';
var
  Found: Boolean;
  LMethod: TRttiMethod;
  LIndex: Integer;
  LParams: TArray<TRttiParameter>;
  i: Integer;
  RTTIContext: TRttiContext;
  RttiType: TRttiType;
begin
  Found := False;
  LMethod := nil;

  if length(ObjectArray) > 0 then
  begin
    RTTIContext := TRttiContext.Create;
    RttiType := RTTIContext.GetType(Instance.ClassInfo);

    for LMethod in RttiType.GetMethods do
    begin
      if SameText(LMethod.Name, AddMethodName) then
      begin
        LParams := LMethod.GetParameters;

        if length(LParams) = 1 then
        begin
          Found := TRUE;

          for LIndex := 0 to length(LParams) - 1 do
          begin
            if LParams[LIndex].ParamType.Handle <> TValue(ObjectArray[0]).TypeInfo
            then
            begin
              Found := False;

              Break;
            end;
          end;
        end;

        if Found then
          Break;
      end;
    end;

    if Found then
    begin
      for i := Low(ObjectArray) to High(ObjectArray) do
      begin
        LMethod.Invoke(Instance, [ObjectArray[i]]);
      end;
    end
    else
    begin
      raise Exception.CreateFmt(StrMethodSNotFound, [AddMethodName]);
    end;
  end;
end;
norgepaul
  • 6,013
  • 4
  • 43
  • 76
  • It would surprise me if this was the best solution to the problem – David Heffernan Mar 02 '15 at 16:10
  • I'm very open to other ideas :) – norgepaul Mar 02 '15 at 16:36
  • 2
    I wouldn't like to say without knowing how you end up having lost track of the type of these objects. – David Heffernan Mar 02 '15 at 16:38
  • 2
    But `TObjectList` **doesn't** have a method `Add(AObject: TObject)`. Its `Add` method requires its parameter to have type `T`, and I don't see where that restriction gets enforced properly. I must echo David in expressing my concern over how you managed to *lose* so much type information in the first place that would prompt you to even need this function. – Rob Kennedy Mar 02 '15 at 16:53
  • I'm writing an ORM. I know, it's been done before :) - but I have a few specific requirements and want to figure out how it's done. The code I've been working on uses RTTI to step through all the published properties of a TObject descendant and loads them from a database. If one of the properties is a class I need to process it. If the class is a list I first load the records into an array of objects, then assign the array to whatever type the class property is. I don't lose the any information, I never have it in the first place. – norgepaul Mar 02 '15 at 17:16
  • Why don't you look at how other ORMs deal with this. – David Heffernan Mar 02 '15 at 17:49
  • @DavidHeffernan - That was my next port of call. – norgepaul Mar 02 '15 at 17:58
  • I took a look at DelphiORM and it seems like it solves the problem in a similar manner: while not reader.Eof do begin TdormUtils.MethodCall(AList, 'Add', [CreateObjectFromUIBQuery(ARttiType, reader, AMappingTable)]); reader.Next; end; – norgepaul Mar 02 '15 at 18:59
  • I agree with Rob Kennedy that checking if certain object class has Add method is not the best way for finding out if that specific object is a list of some kind. Would it be better if you would go and check to see if default objects property is and indexed property instead. – SilverWarior Mar 02 '15 at 19:10
  • Be careful with that approach because that would let you put apples into a list of bananas just because it is a list of object and you are putting objects into it. – Stefan Glienke Mar 02 '15 at 19:16
  • @StefanGlienke - Totally agree, and will add more checks and measures if somebody can suggest how. Also, in my case, the objects which will be loaded are under my control, so I always know I'm dealing with bananas :) – norgepaul Mar 02 '15 at 19:19