Delphi RTTI迭代泛型记录类型的属性
我有几个具有简单类型属性(整型、布尔型、字符串)的类和一些可为null的类:Delphi RTTI迭代泛型记录类型的属性,delphi,generics,delphi-xe2,Delphi,Generics,Delphi Xe2,我有几个具有简单类型属性(整型、布尔型、字符串)的类和一些可为null的类: Nullable<T> = record private FValue: T; FHasValue: IInterface; function GetValue: T; function GetHasValue: Boolean; public constructor Create(AValue: T); property HasValue: Bo
Nullable<T> = record
private
FValue: T;
FHasValue: IInterface;
function GetValue: T;
function GetHasValue: Boolean;
public
constructor Create(AValue: T);
property HasValue: Boolean read GetHasValue;
property Value: T read GetValue;
end;
Nullable=记录
私有的
f值:T;
FHasValue:i接口;
函数GetValue:T;
函数GetHasValue:布尔型;
公众的
构造函数创建(AValue:T);
属性HasValue:布尔读取GetHasValue;
属性值:T读取GetValue;
结束;
例如
TMyClass1=class(tcommon)
私有的
fnumeric值:双倍;
发热时间:可为空;
公开的
属性NumericValue:双读FNumericValue写FNumericValue;
属性EventTime:可为空的读取FEventTime写入FEventTime;
结束;
及
TMyClass2=class(tcommon)
私有的
FCount:可为空;
FName:字符串;
公开的
属性计数:可为空的读FCount写FCount;
属性名称:字符串读取FName写入FName;
结束;
等等
给定TCommonsOrient的后代,我希望使用RTTI迭代所有公共属性并列出它们的名称和值,除非T.HasValue返回false时它是可空的
我使用的是DelphiXe2
编辑:添加到目前为止的内容。
procedure ExtractValues(Item: TCommonAncestor);
var
c : TRttiContext;
t : TRttiType;
p : TRttiProperty;
begin
c := TRttiContext.Create;
try
t := c.GetType(Item.ClassType);
for p in t.GetProperties do
begin
case p.PropertyType.TypeKind of
tkInteger:
OutputDebugString(PChar(Format('%se=%s', [p.Name,p.GetValue(Item).ToString]));
tkRecord:
begin
// for Nullable<Double> p.PropertyType.Name contains 'Nullable<System.Double>'
// but how do I go about accessing properties of this record-type field?
end;
end;
end;
finally
c.Free;
end;
end;
程序提取值(项目:tCommon);
变量
c:语境;
t:trtti型;
p:trtti属性;
开始
c:=TRttiContext.Create;
尝试
t:=c.GetType(Item.ClassType);
对于t.GetProperties中的p
开始
案例p.PropertyType.TypeKind of
特金泰格:
OutputDebugString(PChar(格式(“%se=%s”,[p.Name,p.GetValue(Item.ToString]));
tkRecord:
开始
//对于可为Null的p.PropertyType.Name包含“Nullable”
//但如何访问此记录类型字段的属性呢?
结束;
结束;
结束;
最后
c、 免费的;
结束;
结束;
以下内容在XE2中适用:
uses
System.SysUtils, System.TypInfo, System.Rtti, System.StrUtils, Winapi.Windows;
type
Nullable<T> = record
private
FValue: T;
FHasValue: IInterface;
function GetHasValue: Boolean;
function GetValue: T;
procedure SetValue(const AValue: T);
public
constructor Create(AValue: T);
function ToString: string; // <-- add this for easier use!
property HasValue: Boolean read GetHasValue;
property Value: T read GetValue write SetValue;
end;
TCommonAncestor = class
end;
TMyClass1 = class(TCommonAncestor)
private
FNumericvalue: Double;
FEventTime: Nullable<TDateTime>;
public
property NumericValue: Double read FNumericValue write FNumericValue;
property EventTime: Nullable<TDateTime> read FEventTime write FEventTime;
end;
TMyClass2 = class(TCommonAncestor)
private
FCount: Nullable<Integer>;
FName: string;
public
property Count: Nullable<Integer> read FCount write FCount;
property Name: string read FName write FName;
end;
...
constructor Nullable<T>.Create(AValue: T);
begin
SetValue(AValue);
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> nil;
end;
function Nullable<T>.GetValue: T;
begin
if HasValue then
Result := FValue
else
Result := Default(T);
end;
procedure Nullable<T>.SetValue(const AValue: T);
begin
FValue := AValue;
FHasValue := TInterfacedObject.Create;
end;
function Nullable<T>.ToString: string;
begin
if HasValue then
begin
// TValue.ToString() does not output T(Date|Time) values as date/time strings,
// it outputs them as floating-point numbers instead, so do it manually...
if TypeInfo(T) = TypeInfo(TDateTime) then
Result := DateTimeToStr(PDateTime(@FValue)^)
else if TypeInfo(T) = TypeInfo(TDate) then
Result := DateToStr(PDateTime(@FValue)^)
else if TypeInfo(T) = TypeInfo(TTime) then
Result := TimeToStr(PDateTime(@FValue)^)
else
Result := TValue.From<T>(FValue).ToString;
end
else
Result := '(null)';
end;
procedure ExtractValues(Item: TCommonAncestor);
var
c : TRttiContext;
t : TRttiType;
p : TRttiProperty;
v : TValue;
m : TRttiMethod;
s : string;
begin
c := TRttiContext.Create;
t := c.GetType(Item.ClassType);
for p in t.GetProperties do
begin
case p.PropertyType.TypeKind of
tkRecord:
begin
if StartsText('Nullable<', p.PropertyType.Name) then
begin
// get Nullable<T> instance...
v := p.GetValue(Item);
// invoke Nullable<T>.ToString() method on that instance...
m := c.GetType(v.TypeInfo).GetMethod('ToString');
s := m.Invoke(v, []).AsString;
end else
s := Format('(record type %s)', [p.PropertyName.Name]);
end;
else
s := p.GetValue(Item).ToString;
end;
OutputDebugString(PChar(Format('%s=%s', [p.Name, s])))
end;
end;
到目前为止,您尝试了什么来解决这个问题?公共属性无法通过遗留RTTI(
System.TypInfo
unit)访问,因此您必须使用扩展RTTI(System.RTTI
unit)我已经修改了我的问题,以表明我正在尝试使用System.RTTI访问带有RTTI的记录属性是不可能的。您必须改为访问记录字段。@LU RD您能详细说明一下吗?@LURD:询问如何访问记录类型内的属性,由于记录的属性缺少RTTI,因此确实无法访问RTI。但这不是这个问题的内容。这个问题是问如何访问一个记录类型的属性,并且该属性与RTTI配合良好。@Remy极好的解决方案,非常感谢。你救了我一天。您好@Remy极好的解决方案,但可为null的SetValue呢?如果我在这里提出一个问题,你能帮我回答吗?@Rebelss确切地说,SetValue()
怎么样?你的问题是什么?@Remi在这里,我试着用一个问题的形式来表达:
procedure ExtractValues(Item: TCommonAncestor);
var
c : TRttiContext;
t : TRttiType;
p : TRttiProperty;
begin
c := TRttiContext.Create;
try
t := c.GetType(Item.ClassType);
for p in t.GetProperties do
begin
case p.PropertyType.TypeKind of
tkInteger:
OutputDebugString(PChar(Format('%se=%s', [p.Name,p.GetValue(Item).ToString]));
tkRecord:
begin
// for Nullable<Double> p.PropertyType.Name contains 'Nullable<System.Double>'
// but how do I go about accessing properties of this record-type field?
end;
end;
end;
finally
c.Free;
end;
end;
uses
System.SysUtils, System.TypInfo, System.Rtti, System.StrUtils, Winapi.Windows;
type
Nullable<T> = record
private
FValue: T;
FHasValue: IInterface;
function GetHasValue: Boolean;
function GetValue: T;
procedure SetValue(const AValue: T);
public
constructor Create(AValue: T);
function ToString: string; // <-- add this for easier use!
property HasValue: Boolean read GetHasValue;
property Value: T read GetValue write SetValue;
end;
TCommonAncestor = class
end;
TMyClass1 = class(TCommonAncestor)
private
FNumericvalue: Double;
FEventTime: Nullable<TDateTime>;
public
property NumericValue: Double read FNumericValue write FNumericValue;
property EventTime: Nullable<TDateTime> read FEventTime write FEventTime;
end;
TMyClass2 = class(TCommonAncestor)
private
FCount: Nullable<Integer>;
FName: string;
public
property Count: Nullable<Integer> read FCount write FCount;
property Name: string read FName write FName;
end;
...
constructor Nullable<T>.Create(AValue: T);
begin
SetValue(AValue);
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> nil;
end;
function Nullable<T>.GetValue: T;
begin
if HasValue then
Result := FValue
else
Result := Default(T);
end;
procedure Nullable<T>.SetValue(const AValue: T);
begin
FValue := AValue;
FHasValue := TInterfacedObject.Create;
end;
function Nullable<T>.ToString: string;
begin
if HasValue then
begin
// TValue.ToString() does not output T(Date|Time) values as date/time strings,
// it outputs them as floating-point numbers instead, so do it manually...
if TypeInfo(T) = TypeInfo(TDateTime) then
Result := DateTimeToStr(PDateTime(@FValue)^)
else if TypeInfo(T) = TypeInfo(TDate) then
Result := DateToStr(PDateTime(@FValue)^)
else if TypeInfo(T) = TypeInfo(TTime) then
Result := TimeToStr(PDateTime(@FValue)^)
else
Result := TValue.From<T>(FValue).ToString;
end
else
Result := '(null)';
end;
procedure ExtractValues(Item: TCommonAncestor);
var
c : TRttiContext;
t : TRttiType;
p : TRttiProperty;
v : TValue;
m : TRttiMethod;
s : string;
begin
c := TRttiContext.Create;
t := c.GetType(Item.ClassType);
for p in t.GetProperties do
begin
case p.PropertyType.TypeKind of
tkRecord:
begin
if StartsText('Nullable<', p.PropertyType.Name) then
begin
// get Nullable<T> instance...
v := p.GetValue(Item);
// invoke Nullable<T>.ToString() method on that instance...
m := c.GetType(v.TypeInfo).GetMethod('ToString');
s := m.Invoke(v, []).AsString;
end else
s := Format('(record type %s)', [p.PropertyName.Name]);
end;
else
s := p.GetValue(Item).ToString;
end;
OutputDebugString(PChar(Format('%s=%s', [p.Name, s])))
end;
end;
var
Item1: TMyClass1;
Item2: TMyClass2;
begin
Item1 := TMyClass1.Create;
try
Item1.NumericValue := 123.45;
Item1.EventTime.SetValue(Now);
ExtractValues(Item1);
{ Output:
NumericValue=123.45
EventTime=10/19/2017 1:25:05 PM
}
finally
Item1.Free;
end;
Item1 := TMyClass1.Create;
try
Item1.NumericValue := 456.78;
//Item1.EventTime.SetValue(Now);
ExtractValues(Item1);
{ Output:
NumericValue=456.78
EventTime=(null)
}
finally
Item1.Free;
end;
Item2 := TMyClass2.Create;
try
Item2.Count.SetValue(12345);
Item2.Name := 'test';
ExtractValues(Item2);
{ Output:
Count=12345
Name=test
}
finally
Item2.Free;
end;
Item2 := TMyClass2.Create;
try
//Item2.Count.SetValue(12345);
Item2.Name := 'test2';
ExtractValues(Item2);
{ Output:
Count=(null)
Name=test2
}
finally
Item2.Free;
end;
end;