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

Que*_*low 3 delphi generics rtti typechecking

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

注:我已经知道如何轻松地检查它的确切类型,即具有.BaseType相应的财产TRttiTypeTValue,造成例如TList<string>,但我想测试比较,如果它是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
Run Code Online (Sandbox Code Playgroud)

而且,如果我还能检测出它是哪种类型,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<>`
Run Code Online (Sandbox Code Playgroud)

我试过了:

if obj_1_rtti_type is TRttiEnumerationType then
begin
   //...
end;
Run Code Online (Sandbox Code Playgroud)

但由于某种原因,这个评估false,我完全不知道为什么会这样?在这种情况下,表达式value_type.BaseType.Name确实会评估'TEnumerable<System.string>',但实际上必须有一些其他方法,而不是手动解析此字符串以实现我的目标,对吧?

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

Rem*_*eau 5

没有为通用类型本身生成RTTI(它们在运行时不存在),并且每个特定实例化(例如TList<string>)是具有其自己的不同RTTI不同类类型.您必须检查每种类型,无法测试任何通用类型.解析类名是检测泛型类型的唯一方法.

  1. 用于TRttiType.Name将类名称作为字符串('TList<System.string>').

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

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

  4. 走祖先树寻找一个祖先,其TRttiType.Name'TEnumerable<...>',这里...是所提取的子('TEnumerable<System.string>').

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

type
  TMyClass = class(TEnumerable<string>)
  end;
Run Code Online (Sandbox Code Playgroud)

要考虑到这一点,请忽略步骤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;
Run Code Online (Sandbox Code Playgroud)

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