2017-10-19 143 views
0

我有幾個班,(整數,布爾,字符串)和一些可空的簡單類型的屬性:德爾福RTTI迭代通用的記錄類型的屬性

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; 

EG。

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; 

等....

鑑於TCommonAncestor的後裔,我想用RTTI來遍歷所有的公共屬性,並列出他們的名字和值,除非它是T.HasValue返回false的可空。

我正在使用Delphi XE2。

編輯:增加了我到目前爲止。在XE2我

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; 
+3

而你嘗試過什麼迄今爲止解決這個問題?公共屬性不能通過傳統的RTTI('System.TypInfo'單位)訪問,所以你必須使用擴展RTTI('System.Rtti'單位) –

+0

我修改了我的問題來表明我正在嘗試使用System.Rtti – TheRoadrunner

+0

使用RTTI訪問記錄屬性是不可能的。你將不得不去記錄字段。 –

回答

1

以下工作:

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; 
+0

如果設置了正確的RTTI可見性聲明,則可以訪問私有記錄方法「GetValue」和「GetHasValue」。無論如何,用RTTI無法直接訪問可空的記錄屬性的問題通過添加一個公共方法來解決。 –

+0

@Remy優秀的解決方案,非常感謝。你拯救了我的一天。 – TheRoadrunner