How do I cast a TObject as a TObjectList<T>?
Asked Answered
H

2

5

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?

Horsemint answered 2/3, 2015 at 16:6 Comment(1)
Downvoting seems a little harsh. Perhaps the downvoters can say why?Horsemint
M
6

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;
Monotone answered 2/3, 2015 at 19:39 Comment(2)
Massively off topic, but how can pred(Count) be considered better than Count-1?Snap
@DavidHeffernan, old habits from TP, where Succ/Pred perhaps was better optimized than +/- 1. Perhaps, because I'm not sure I remember correctly.Barriebarrientos
H
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;
Horsemint answered 2/3, 2015 at 16:7 Comment(11)
It would surprise me if this was the best solution to the problemSnap
I'm very open to other ideas :)Horsemint
I wouldn't like to say without knowing how you end up having lost track of the type of these objects.Snap
But TObjectList<T> 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.Marcasite
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.Horsemint
Why don't you look at how other ORMs deal with this.Snap
@DavidHeffernan - That was my next port of call.Horsemint
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;Horsemint
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.Byrn
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.Monotone
@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 :)Horsemint

© 2022 - 2024 — McMap. All rights reserved.