我正在使用RTTI实现用于流式传输任意Delphi对象的通用代码,并且为了使其工作(更具体地说,为了使加载部分工作),我需要以某种方式获得子项类型不使用任何实际对象实例的TObjectList<T>
字段。
要求不使用任何实际对象实例的明显原因是,在从流加载对象的情况下(仅基于要加载的对象的类类型的知识),我不会在加载完成之前有任何实例可用 - 我宁愿只能访问相关类的纯RTTI数据。
我希望能够加载的这类课程的示例如下:
TTestClass = class(TObject)
public
test_list : TList<string>;
end;
我想要的是能够得出结论:test_list
字段是TList<T>
,其中T
是string
(即为了知道预期的数据)来自子项的流)。
如果该课程的内容如下:
TTestClassWithArr = class(TObject)
public
test_arr : array of string;
end;
我可以使用ElementType()
字段的TRttiDynamicArrayType
RTTI类的test_arr
方法纯粹通过RTTI提取此信息,但我找不到任何相应的此类显式RTTI类型{ {1}}。
另一个Stack Overflow问题(Delphi Rtti: how to get objects from TObjectList<T>
)是相关的,但确实使用RTTI数据反映的对象的实际实例来“欺骗”以获取子项,这又是对我来说不是一个选择,因为在我必须知道的时候这些子项目不存在。
真的感觉应该通过单独使用类的RTTI信息来实现这一点,因为所有类型信息显然都是在编译时为它提供的,无论对象实例化如何。
答案 0 :(得分:3)
不幸的是,没有为通用参数生成RTTI。在T
等通用容器中发现TList<T>
值的唯一方法是获取目标字段本身的TRttiType
,调用其ToString()
方法获取其类将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;
将Generic参数解压缩为字符串后,如果需要访问该类型的RTTI,则可以使用TRttiContext.FindType()
。
话虽如此,下面的代码提供了一堆RTTI助手:
DSharp.Core.Reflection.pas(Google代码)
DSharp.Core.Reflection.pas(BitBucket)
除此之外,它还定义了一个TRttiTypeHelper
类助手,可以将GetGenericArguments()
方法添加到TRttiType
:
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;
答案 1 :(得分:2)
我一直在寻找解决方案,我有一个想与你分享的想法。该解决方案使用rtti并从列表(TList,TObjectList等)获取方法的参数“Add”。在我的函数中,我只返回类类型,但您可以轻松地实现到primivite类型。我希望它可以帮助别人。问候。
folow中:
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;