我如何测试未知的 Delphi RTTI TValue 是否反映了任何类型的通用 TList<>(或至少是 TEnumerable<>)的对象?

How can I test if an unknown Delphi RTTI TValue reflects an object that is ANY type of generic TList<> (or at least TEnumerable<>)?

在 Delphi 中,如果我有一个反映未知对象的 TValue 实例,我如何测试该对象是否是任何类型的通用 TEnumerable<>(甚至更好的是,它也是 哪个 特定泛型可枚举类型的一个实例,例如 TList<>)?

注意:我已经知道如何轻松检查其 确切 类型,即使用相应 .BaseType 属性 TValue 的 =18=],导致例如 TList<string>,但我想测试的是它是否是 any[=45= 的 TList<> ] 子项类型.

为了举例说明这个假设的代码 "IsAnyKindOfGenericEnumerable()" 是如何工作的,下面是一些示例代码:

var
   LContext : TRttiContext;
   obj_1_rtti_value : TValue;
   obj_2_rtti_value : TValue;
   obj_3_rtti_value : TValue;
   obj_1_rtti_type : TRttiType;
   obj_2_rtti_type : TRttiType;
   obj_3_rtti_type : TRttiType;

LContext := TRttiContext.Create();

{
...
obj_1_rtti_value is set to a TValue reflection of a TList<string> object here
obj_2_rtti_value is set to a TValue reflection of a plain TObject object here
obj_3_rtti_value is set to a TValue reflection of a TQueue<integer> object here
...
}

obj_1_rtti_type := LContext.GetType(obj_1_rtti_value.TypeInfo);
obj_2_rtti_type := LContext.GetType(obj_2_rtti_value.TypeInfo);
obj_3_rtti_type := LContext.GetType(obj_3_rtti_value.TypeInfo);

IsAnyKindOfGenericEnumerable(obj_1_rtti_type); //Would return true
IsAnyKindOfGenericEnumerable(obj_2_rtti_type); //Would return false
IsAnyKindOfGenericEnumerable(obj_3_rtti_type); //Would return true

再一次,最好的事情是,如果我也能检测到 哪种 它是 TEnumerable<> 类型,例如:

IsAnyKindOfGenericEnumerable(obj_1_rtti_type); //Will return true + `TList<>`
IsAnyKindOfGenericEnumerable(obj_2_rtti_type); //Will return false
IsAnyKindOfGenericEnumerable(obj_3_rtti_type); //Will return true + `TQueue<>`

我试过:

if obj_1_rtti_type is TRttiEnumerationType then
begin
   //...
end;

但出于某种原因,它的计算结果为 false,我完全不知道为什么会这样?在这种情况下,表达式 value_type.BaseType.Name 确实计算为 'TEnumerable<System.string>',但除了手动解析此字符串以完成我的 objective 之外,确实必须有其他方法,对吧?

最后,目标必须仅使用 RTTI 信息来实现,也就是说,任何通过引用 TValue 背后的真实对象的 "cheating" 都是不允许的(出于本问题范围之外的原因)。

没有为泛型类型本身生成 RTTI(它们在运行时不存在),每个特定的实例化(如 TList<string>)都是一个 distinct class 类型有自己独特的 RTTI。您必须检查每个单独的类型,不可能测试任何通用类型。解析 class 名称是检测通用类型的唯一方法。

  1. 使用 TRttiType.Name 获取 class 名称作为字符串 ('TList<System.string>')。

  2. 解析它以检测尖括号 ('<>') 的存在。

  3. 提取括号之间的子串('System.string')

  4. 遍历祖先树寻找 TRttiType.Name'TEnumerable<...>' 的祖先,其中 ... 是提取的子字符串 ('TEnumerable<System.string>').

但是,对于从 TEnumerable<T> 派生但本身没有泛型参数的 class 类型,此方法失败,例如:

type
  TMyClass = class(TEnumerable<string>)
  end;

为了解决这个问题,请忽略第 1-3 步并直接直接跳到第 4 步,忽略括号中出现的任何值,例如:

function IsAnyKindOfGenericEnumerable(AType: TRttiType): Boolean;
begin
  Result := False;
  while AType <> nil do
  begin
    Result := StartsText('TEnumerable<', AType.Name);
    if Result then Exit;
    AType := AType.BaseType;
  end;
end;

至于TRttiEnumerationType,则代表enumerated types(即:type typeName = (val1, ...,valn);)。与TEnumerable<T>无关。这就是为什么 is 运算符总是为您返回 False - 您正在测试的 RTTI 类型中的 none 代表枚举。