【问题标题】:How do I cast a TObject as a TObjectList<T>?如何将 TObject 转换为 TObjectList<T>?
【发布时间】:2015-03-02 16:06:56
【问题描述】:

我有一个程序需要将TObjects 的数组插入到列表中。该列表可以是任何受支持的类型,例如TObjectList、TObjectList&lt;T&gt;、TROArray等

过程如下:

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;

如何检查对象是否为TObjectList&lt;T&gt;,然后将数组的元素添加到其中?

【问题讨论】:

  • 拒绝投票似乎有点苛刻。也许反对者可以说出原因?

标签: delphi generics rtti delphi-xe7


【解决方案1】:

您必须使用一点 RTTI 来获取有关泛型类型的更多信息。

以下代码使用 Spring4D,其中有一些方法:

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;

除此之外,您还可以获取有关列表的通用参数类型的信息,以检查您放入其中的对象是否符合要求(当然仅适用于通用类型):

function GetGenericTObjectListParameter(const obj: TObject): TClass;
var
  t: TRttiType;
begin
  t := TType.GetType(obj.ClassInfo);
  Result := t.GetGenericArguments[0].AsInstance.MetaclassType;
end;

【讨论】:

  • 大大偏离主题,但pred(Count) 怎么能被认为比Count-1 更好?
  • @DavidHeffernan,来自 TP 的旧习惯,其中 Succ/Pred 的优化可能比 +/- 1 更好。也许是因为我不确定我是否记错了。
【解决方案2】:

在我写这个问题时,我想出了一种使用 RTTI 的方法。它应该适用于任何具有过程 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;

【讨论】:

  • 如果这是解决问题的最佳方法,我会感到惊讶
  • 我对其他想法持开放态度:)
  • 在不知道您最终如何忘记这些对象的类型的情况下,我不想说。
  • 但是TObjectList&lt;T&gt; 没有有一个方法Add(AObject: TObject)。它的Add 方法要求它的参数具有T 类型,我看不出该限制在哪里得到正确执行。我必须回应 David,表达了我对您如何设法丢失这么多类型信息的担忧,这会促使您甚至需要此功能。
  • 我正在写一个 ORM。我知道,它以前做过:) - 但我有一些具体要求,想弄清楚它是如何完成的。我一直在编写的代码使用 RTTI 逐步遍历 TObject 后代的所有已发布属性并从数据库中加载它们。如果其中一个属性是一个类,我需要处理它。如果类是一个列表,我首先将记录加载到对象数组中,然后将数组分配给类属性的任何类型。我不会丢失任何信息,我从一开始就没有。
猜你喜欢
  • 1970-01-01
  • 2017-08-18
  • 1970-01-01
  • 1970-01-01
  • 2014-02-05
  • 1970-01-01
  • 2016-02-29
  • 2019-06-13
  • 2010-11-18
相关资源
最近更新 更多