如何在 Delphi 中仅通过 RTTI 信息(即不使用任何实际对象实例)获取 TObjectList<T> 的子项类型?
How can I get the sub-item type of a TObjectList<T> purely by RTTI information (i.e. without using any actual object instance) in Delphi?
我正在实现使用 RTTI 流式传输任意 Delphi 对象的通用代码,为了让它工作(更具体地说,为了让加载部分工作),我需要以某种方式在不使用任何实际对象实例的情况下获取 TObjectList<T>
字段的子项类型。
要求不使用任何实际对象实例的明显原因是,在从流加载对象的情况下(仅基于要加载的对象的 class 类型的知识), 在加载完成之前我根本没有任何可用实例 - 我宁愿只能访问相关 class 的纯 RTTI 数据。
我希望能够加载的 class 示例如下:
TTestClass = class(TObject)
public
test_list : TList<string>;
end;
我想要的是能够得出结论,test_list
字段是一个通用的 TList<T>
,其中 T
是 string
(即为了知道什么数据期望从流中获取子项)。
如果 class 确实如下所示:
TTestClassWithArr = class(TObject)
public
test_arr : array of string;
end;
我可以使用 TRttiDynamicArrayType
RTTI class 的 test_arr
字段的 ElementType()
方法来纯粹通过 RTTI 提取此信息,但我找不到任何对应的TObjectList<T>
.
的显式 RTTI 类型
另一个堆栈溢出问题 (Delphi Rtti: how to get objects from TObjectList<T>
) 是相关的,但确实使用了 RTTI 数据反映到 "cheat" 的对象的实际实例以获取子项,同样,这对我来说不是一个选择,因为当时我必须知道这些子项目不存在。
虽然感觉应该有某种方法可以通过单独使用 class 的 RTTI 信息来做到这一点,因为所有类型信息在编译时显然都存在,无论对象如何实例化。
很遗憾,没有为通用参数生成 RTTI。在像 TList<T>
这样的通用容器中发现 T
的值的唯一方法是获取目标字段本身的 TRttiType
,调用它的 ToString()
方法来获取它的 class name 为字符串,解析出括号内的子串。例如:
uses
..., System.StrUtils, System.Rtti;
var
Ctx: TRttiContext;
s: string;
OpenBracket, CloseBracket: Integer;
...
begin
...
s := Ctx.GetType(TTestClass).GetField('test_list').FieldType.ToString; // returns 'TList<System.string>'
OpenBracket := Pos('<', s);
CloseBracket := PosEx('>', s, OpenBracket+1);
s := Copy(s, OpenBracket+1, CloseBracket-OpenBracket-1); // returns 'System.string'
// if multiple Generic parameters are present, they will be separated by commas...
...
end;
将通用参数提取为字符串后,如果需要访问该类型的 RTTI,则可以使用 TRttiContext.FindType()
。
话虽如此,以下代码提供了一堆 RTTI 助手:
DSharp.Core.Reflection.pas(Google代码)
DSharp.Core.Reflection.pas(比特桶)
除其他外,它定义了一个 TRttiTypeHelper
class 帮助器,它向 TRttiType
:
添加了一个 GetGenericArguments()
方法
TRttiTypeHelper = class helper for TRttiType
...
public
...
function GetGenericArguments: TArray<TRttiType>;
...
end;
在内部,GetGenericArguments()
使用我上面提到的相同技术。有了它,您可以改为这样做:
uses
..., System.Rtti, DSharp.Core.Reflection;
var
Ctx: TRttiContext;
arr: TArray<TRttiType>;
typ: TRttiType;
s: string;
...
begin
...
arr := Ctx.GetType(TTestClass).GetField('test_list').FieldType.GetGenericArguments;
typ := arr[0]; // returns RTTI for System.String
s := typ.ToString; // returns 'System.string'
...
end;
我一直在寻找解决方案,我有一个想法想与您分享。该解决方案使用 rtti 并从列表(TList、TObjectList 等)中获取方法的参数 "Add"。在我的函数中,我只是 return Class 类型,但您可以轻松实现原始类型。我希望它可以帮助某人。问候。
关注:
class function TUtilRtti.GetSubTypeItemClassFromList(ObjectList: TObject): TClass;
var
ctxRtti : TRttiContext;
typeRtti : TRttiType;
atrbRtti : TCustomAttribute;
methodRtti: TRttiMethod;
parameterRtti: TRttiParameter;
begin
result := nil;
ctxRtti := TRttiContext.Create;
typeRtti := ctxRtti.GetType( ObjectList.ClassType );
methodRtti := typeRtti.GetMethod('Add');
for parameterRtti in methodRtti.GetParameters do
begin
if SameText(parameterRtti.Name,'Value') then
begin
if parameterRtti.ParamType.IsInstance then
result := parameterRtti.ParamType.AsInstance.MetaclassType;
break;
end;
end;
ctxRtti.Free;
end;
样本
...
var
List: TList<TCustomer>;
begin
List := TList<Customer>.Create();
ShowMessage(TUtilRtti.GetSubTypeItemClassFromList(List).ClassName);
end;
我正在实现使用 RTTI 流式传输任意 Delphi 对象的通用代码,为了让它工作(更具体地说,为了让加载部分工作),我需要以某种方式在不使用任何实际对象实例的情况下获取 TObjectList<T>
字段的子项类型。
要求不使用任何实际对象实例的明显原因是,在从流加载对象的情况下(仅基于要加载的对象的 class 类型的知识), 在加载完成之前我根本没有任何可用实例 - 我宁愿只能访问相关 class 的纯 RTTI 数据。
我希望能够加载的 class 示例如下:
TTestClass = class(TObject)
public
test_list : TList<string>;
end;
我想要的是能够得出结论,test_list
字段是一个通用的 TList<T>
,其中 T
是 string
(即为了知道什么数据期望从流中获取子项)。
如果 class 确实如下所示:
TTestClassWithArr = class(TObject)
public
test_arr : array of string;
end;
我可以使用 TRttiDynamicArrayType
RTTI class 的 test_arr
字段的 ElementType()
方法来纯粹通过 RTTI 提取此信息,但我找不到任何对应的TObjectList<T>
.
另一个堆栈溢出问题 (Delphi Rtti: how to get objects from TObjectList<T>
) 是相关的,但确实使用了 RTTI 数据反映到 "cheat" 的对象的实际实例以获取子项,同样,这对我来说不是一个选择,因为当时我必须知道这些子项目不存在。
虽然感觉应该有某种方法可以通过单独使用 class 的 RTTI 信息来做到这一点,因为所有类型信息在编译时显然都存在,无论对象如何实例化。
很遗憾,没有为通用参数生成 RTTI。在像 TList<T>
这样的通用容器中发现 T
的值的唯一方法是获取目标字段本身的 TRttiType
,调用它的 ToString()
方法来获取它的 class name 为字符串,解析出括号内的子串。例如:
uses
..., System.StrUtils, System.Rtti;
var
Ctx: TRttiContext;
s: string;
OpenBracket, CloseBracket: Integer;
...
begin
...
s := Ctx.GetType(TTestClass).GetField('test_list').FieldType.ToString; // returns 'TList<System.string>'
OpenBracket := Pos('<', s);
CloseBracket := PosEx('>', s, OpenBracket+1);
s := Copy(s, OpenBracket+1, CloseBracket-OpenBracket-1); // returns 'System.string'
// if multiple Generic parameters are present, they will be separated by commas...
...
end;
将通用参数提取为字符串后,如果需要访问该类型的 RTTI,则可以使用 TRttiContext.FindType()
。
话虽如此,以下代码提供了一堆 RTTI 助手:
DSharp.Core.Reflection.pas(Google代码)
DSharp.Core.Reflection.pas(比特桶)
除其他外,它定义了一个 TRttiTypeHelper
class 帮助器,它向 TRttiType
:
GetGenericArguments()
方法
TRttiTypeHelper = class helper for TRttiType
...
public
...
function GetGenericArguments: TArray<TRttiType>;
...
end;
在内部,GetGenericArguments()
使用我上面提到的相同技术。有了它,您可以改为这样做:
uses
..., System.Rtti, DSharp.Core.Reflection;
var
Ctx: TRttiContext;
arr: TArray<TRttiType>;
typ: TRttiType;
s: string;
...
begin
...
arr := Ctx.GetType(TTestClass).GetField('test_list').FieldType.GetGenericArguments;
typ := arr[0]; // returns RTTI for System.String
s := typ.ToString; // returns 'System.string'
...
end;
我一直在寻找解决方案,我有一个想法想与您分享。该解决方案使用 rtti 并从列表(TList、TObjectList 等)中获取方法的参数 "Add"。在我的函数中,我只是 return Class 类型,但您可以轻松实现原始类型。我希望它可以帮助某人。问候。
关注:
class function TUtilRtti.GetSubTypeItemClassFromList(ObjectList: TObject): TClass;
var
ctxRtti : TRttiContext;
typeRtti : TRttiType;
atrbRtti : TCustomAttribute;
methodRtti: TRttiMethod;
parameterRtti: TRttiParameter;
begin
result := nil;
ctxRtti := TRttiContext.Create;
typeRtti := ctxRtti.GetType( ObjectList.ClassType );
methodRtti := typeRtti.GetMethod('Add');
for parameterRtti in methodRtti.GetParameters do
begin
if SameText(parameterRtti.Name,'Value') then
begin
if parameterRtti.ParamType.IsInstance then
result := parameterRtti.ParamType.AsInstance.MetaclassType;
break;
end;
end;
ctxRtti.Free;
end;
样本
...
var
List: TList<TCustomer>;
begin
List := TList<Customer>.Create();
ShowMessage(TUtilRtti.GetSubTypeItemClassFromList(List).ClassName);
end;