How to loop all properties in a Class

OZ8HP picture OZ8HP · Apr 17, 2012 · Viewed 12.4k times · Source

I have a class in my Delphi app where I would like an easy and dynamic way of resetting all the string properties to '' and all the boolean properties to False As far as I can see on the web it should be possible to make a loop of some sort, but how to do it isn't clear to me.

Answer

teran picture teran · Apr 17, 2012

if you are an Delphi 2010 (and higher) user then there is a new RTTI unit (rtti.pas). you can use it to get runtime information about your class and its properties (public properties by default, but you can use {$RTTI} compiler directive to include protected and private fields information). For example we have next test class with 3 public fields (1 boolean and 2 string fields (one of them is readonly)).

    TTest = class(TObject)
      strict private
        FString1 : string;
        FString2 : string;
        FBool : boolean;
      public
        constructor Create();
        procedure PrintValues();

        property String1 : string read FString1 write FString1;
        property String2 : string read FString2;
        property BoolProp : boolean read FBool write FBool;
    end;

constructor TTest.Create();
begin
    FBool := true;
    FString1 := 'test1';
    FString2 := 'test2';
end;

procedure TTest.PrintValues();
begin
    writeln('string1 : ', FString1);
    writeln('string2 : ', FString2);
    writeln('bool: ', BoolToStr(FBool, true));
end;

to enumerate all properties of object and set it values to default you can use something like code below. First at all you have to init TRttiContext structure (it is not neccesary, because it is a record). Then you should get rtti information about your obejct, after that you can loop your properties and filter it (skip readonly properties and other than boolean and stirng). Take into account that there are few kind of strings : tkUString, tkString and others (take a look at TTypeKind in typinfo.pas)

    TObjectReset = record
      strict private
      public
        class procedure ResetObject(obj : TObject);  static;
    end;

{ TObjectReset }

class procedure TObjectReset.ResetObject(obj: TObject);
var ctx : TRttiContext;
    rt : TRttiType;
    prop : TRttiProperty;
    value : TValue;
begin
    ctx := TRttiContext.Create();
    try
        rt := ctx.GetType(obj.ClassType);

        for prop in rt.GetProperties() do begin
            if not prop.IsWritable then continue;

            case prop.PropertyType.TypeKind of
                tkEnumeration : value := false;
                tkUString :      value := '';
                else continue;
            end;
            prop.SetValue(obj, value);
        end;
    finally
        ctx.Free();
    end;
end;

simple code to test:

var t : TTest;
begin
    t := TTest.Create();
    try
        t.PrintValues();
        writeln('reset values'#13#10);
        TObjectReset.ResetObject(t);
        t.PrintValues();
    finally
        readln;
        t.Free();
    end;
end.

and result is

string1 : test1
string2 : test2
bool: True
reset values

string1 :
string2 : test2
bool: False

also take a look at Attributes, imo it is good idea to mark properties (wich you need to reset) with some attribute, and may be with default value like:

[ResetTo('my initial value')]
property MyValue : string read FValue write FValue;

then you can filter only properties wich are marked with ResetToAttribute