我如何测试未知的 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 名称是检测通用类型的唯一方法。
使用 TRttiType.Name
获取 class 名称作为字符串 ('TList<System.string>'
)。
解析它以检测尖括号 ('<>'
) 的存在。
提取括号之间的子串('System.string'
)
遍历祖先树寻找 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 代表枚举。
在 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 名称是检测通用类型的唯一方法。
使用
TRttiType.Name
获取 class 名称作为字符串 ('TList<System.string>'
)。解析它以检测尖括号 (
'<>'
) 的存在。提取括号之间的子串(
'System.string'
)遍历祖先树寻找
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 代表枚举。