Saturday, September 12, 2009

Exploring TRttiType and descendants in Depth

TRttiType provides us with an easy to use interface that allows us to the access all of the RTTI Information associated with that type. For the most part, I find this very intuitive, and self documenting, I recommend opening Rtti.pas and looking at the declaration for TRttiType.

I introduced TRttiType in the previous articles.

Like I stated in a prior article, if your type supports, Fields, Properties, and/or Methods. There are a few easy access method to get access to these.

These methods provide access to all of the Fields, Properties, and Methods that have RTTI information, in up coming articles I will go in depth on TRttiField, TRttiProperty, and TRttiMethod.

function GetMethods: TArray<TRttiMethod>; overload; virtual;
function GetFields: TArray<TRttiField>; virtual;
function GetProperties: TArray<TRttiProperty>; virtual;

function GetMethod(const AName: string): TRttiMethod; virtual;
function GetMethods(const AName: string): TArray<TRttiMethod>; overload; virtual;
function GetField(const AName: string): TRttiField; virtual;
function GetProperty(const AName: string): TRttiProperty; virtual;

However, sometimes you one want to access what was declared on that specific type and not in the parent types, this can be done with the following functionality.


function GetDeclaredMethods: TArray<TRttiMethod>; virtual;
function GetDeclaredProperties: TArray<TRttiProperty>; virtual;
function GetDeclaredFields: TArray<TRttiField>; virtual;


If your type supports ancestors, you can get the BaseType through the base type property.
The following code shows how you can walk back up the type tree to find the parent types.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI;
type
TOneObject = Class(TObject)
end;

TTwoObject = Class(TOneObject)
end;

TThreeObject = Class(TTwoObject)
end;

var
c : TRttiContext;
t : TRttiType;

begin
c := TRttiContext.Create;
try
t := c.GetType(TThreeObject);
writeln(t.Name);
while Assigned(t.BaseType) do
begin
t := t.BaseType;
writeln(t.Name);
end;
finally
c.Free
end;
readln;
end.

Output:

TThreeObject
TTwoObject
TOneObject
TObject



There are 3 sets of properties that give you more information about the given type.


property AsInstance: TRttiInstanceType read GetAsInstance;
property IsInstance: Boolean read GetIsInstance;
property AsOrdinal: TRttiOrdinalType read GetAsOrdinal;
property IsOrdinal: Boolean read GetIsOrdinal;
property IsSet: Boolean read GetIsSet;
property AsSet: TRttiSetType read GetAsSet;


Instance Types are Classes, and the TRttiInstanceType provides the property "MetaclassType" which returns the TClass for the given type. The following example shows how to use this type to construct an instance of an object.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI;
type
TOneObject = Class(TObject)
end;

TTwoObject = Class(TOneObject)
end;

TThreeObject = Class(TTwoObject)
end;

var
c : TRttiContext;
t : TRttiType;
o : TObject;
begin
c := TRttiContext.Create;
try
t := c.GetType(TThreeObject);
o := t.AsInstance.MetaclassType.Create;
Writeln(o.ClassName);
o.Free;
finally
c.Free
end;
readln;
end.

Output:

TThreeObject


TRttiOrdinalType handles Ordinal Types, such as Integer, Enumerated Type, etc...
It exposes 3 new properties to help you when working with Ordinal types.


TOrdType = (otSByte, otUByte, otSWord, otUWord, otSLong, otULong);

...

property OrdType: TOrdType read GetOrdType;
property MinValue: Longint read GetMinValue;
property MaxValue: Longint read GetMaxValue;


The following example code shows how they behave.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI,TypInfo;
type
TMyEnum = (enOne,enTwo,enThree);
var
c : TRttiContext;
t : TRttiType;
begin
c := TRttiContext.Create;
try
t := c.GetType(TypeInfo(TMyEnum));
writeln(GetEnumName(TypeInfo(TOrdType),ord(t.AsOrdinal.OrdType)));
writeln(t.AsOrdinal.MinValue);
writeln(t.AsOrdinal.MaxValue);
finally
c.Free
end;
readln;
end.

Output:

otUByte
0
2


Notice, I dipped back into TypInfo.pas to call GetEnumName(), it and it's partner function GetEnumValue(), allow you to work with the names of an enumerated type instead of the ordinal values.

The previous RTTI information that was available in prior versions of Delphi used a pointer to the type information i.e "pTypeInfo"

This is now stored in the .Handle property of the TRttiType, I mention this as the routines in TypInfo.pas still work if you need drop to some lower level functionality.

The TRttiSetType provides one new property "ElementType" which allows you to get the type of the elements of the set. The following code shows an example of this in action.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI,TypInfo;
type
TMyEnum = (enOne,enTwo,enThree);
TMySet = set of TMyEnum;
var
c : TRttiContext;
t : TRttiType;
begin
c := TRttiContext.Create;
try
t := c.GetType(TypeInfo(TMySet));
Writeln('Element Type:');
writeln(t.AsSet.ElementType.ToString);
finally
c.Free
end;
readln;
end.

Output:

Element Type:
TMyEnum



To be complete the following are provided on TRttiType, but TRttiRecordType only provides one new property, ManagedFields, however I have not found a reason I to need this property.


property AsRecord: TRttiRecordType read GetAsRecord;
property IsRecord: Boolean read GetIsRecord;


But, don't overlook IsRecord as it's very useful.

There are also several other descendant that you can use the standard "is" and "as" functions on get access to additional information associated with the given type.

TRttiInterfaceType
TRttiInt64Type
TRttiMethodType
TRttiClassRefType
TRttiEnumerationType
TRttiStringType
TRttiAnsiStringType
TRttiFloatType
TRttiArrayType
TRttiDynamicArrayType
TRttiPointerType
TRttiProcedureType

The following code shows how you type cast a TRttiType to a TRttiStringType,
to determine what type of string it's associated with.


program Project10;

{$APPTYPE CONSOLE}

uses
SysUtils, RTTI,TypInfo;
type
TmyRecord = record
UniStr : String;
AnsiStr : AnsiString;
WideStr : WideString;
end;
var
c : TRttiContext;
t : TRttiType;
field : TRttiField;
begin
c := TRttiContext.Create;
try
for field in c.GetType(TypeInfo(TMyRecord)).GetFields do
begin
t := field.FieldType;
writeln('Field:',field.Name);
writeln('RttiType:',t.ClassName);
if (t is TRttiStringType) then
Writeln('String Kind:',GetEnumName(TypeInfo(TRttiStringKind),ord((t as TRttiStringType).StringKind)));
Writeln;
end;
finally
c.Free
end;
readln;
end.

Output:

Field:UniStr
RttiType:TRttiStringType
String Kind:skUnicodeString

Field:AnsiStr
RttiType:TRttiAnsiStringType
String Kind:skAnsiString

Field:WideStr
RttiType:TRttiStringType
String Kind:skWideString




In conclusion, there are several other small features of TRttiType but I am not trying to reinvent the documentation, so I will leave that for you to discover.

RTTI Article List

8 comments:

  1. Nice article, thank you. It would be nice if you could post the output of your sample programms along after each code snippet.

    ReplyDelete
  2. Is it also possible to access all types in a unit and all units in a module? This would be a "killer" feature for us. Currenty we use D7/D2007 RTTI possibilities extensively and the most annoying two things are: you have to register your type somewhere in an initialization before we can use it and record types are not supported (thus no TGUID!).

    ReplyDelete
  3. Mignoz: I will see what I can do.

    Ritsaert: YES... The TRTTIContext, is access method to all types that have RTTI information enabled. That is on by default it's controled by $RTTI defined that I discussed in one of the first blog posts on RTTI. So you have access to the information you have been lacking.

    ReplyDelete
  4. Mignoz: I have updated the article to contain output. I am in the process of putting it in the previous articles.

    ReplyDelete
  5. Thanks Robert! I just started an upgrade project from Delphi 5 to Delphi 2010 and your blog is invaluable.

    Is it possible to use RTTI to:
    1. Identify an object as a TEnumerable<T> descendant? I am currently checking RTTIType.GetMethod('GetEnumerator')...
    2. Investigate the contents of the collection as TValues.

    Thanks!

    ReplyDelete
  6. Forget my last post... I just read some more and stumbled onto your TEnumerableFactory - exactly what I needed!

    ReplyDelete
  7. Hi Rob,

    thank you very, very much for your work and effort. Your blog section about RTTI in Delphi was the sole and sufficient source for all my adventures in to RTTI so far.

    However, you may want to consider adding some clarification of a pitfall which probably most users will fall into when adapting your examples.

    The call
    RttiType.AsInstance.MetaclassType.Create

    bears a huge drawback becaus it does not call the constructor of the class held by RttiType but the constructor of TObject, if any. Any statements contained in the constructor of the derived class will therefore not be executed.

    The solution to this problem is also provided by you in another section, i.e. use

    RttiType.GetMethod('Create').Invoke(t.AsInstance.MetaclassType,[]);

    instead.

    Thanks again and keep up the great work!
    Malte.

    ReplyDelete