V nových Delphi 2010 bylo konečně vylepšeno RTTI (Run Time Type Information - tj. informace o typech získané za běhu).
Jen ve zkratce dřívější stav: pokud při deklaraci třídy uvedete published, budou členy v této skupině mít sice viditelnost jako public, ale kompilátor k nim navíc přigeneruje speciální RTTI informace.
Tyto informace používá Delphi např. pro ukládání a načítání DFM souborů, zobrazování dat v Object Inspectoru a další. Kromě toho se pomocí několika metod dají používat i ve Vašem kódu (např. ClassInfo, GetTypeData, GetPropInfo, GetPropList, GetEnumName, GetEnumValue …).
uses Typinfo;
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
Count, Loop: Integer;
List: TPropList;
begin
Count := GetPropList(TypeInfo(TForm1), tkAny, @List);
Listbox1.Items.BeginUpdate;
for Loop := 0 to Pred(Count) do
Listbox1.Items.Add(List[Loop]^.Name);
Listbox1.Items.EndUpdate;
end;
nebo
ShowMessage(GetEnumName(TypeInfo(TFormBorderStyle), Ord(BorderStyle)));
Podrobněji o originálním RTTI zde spolu s pěknými příklady.
RTTI a Delphi 2010
Kromě původních RTTI je v Delphi 2010 přidána možnost Atributů jako má třeba C#. Všechny atributy jsou následníkem TCustomAttribute a mohou tudíž mít metody, property atd. (viz. třída MyAttribute v příkladu).
Příklad ukazuje jednoduché dotazování RTTI (nezapomenout že veškerá podpora je v rtti.pas). V reálném případě by TMyClass pak byl objekt a v proceduře Test by se pracovalo i s tímto objektem (resp. jeho daty). Cílem by bylo uložení dat objektu TMyClass za pomoci RTTI do datového úložiště.
Pro zajímavost dle komentáře autora implementace v Delphi (Barry Kelly), je TRttiContext record zapouzdřující interface a je vytvořen při přístupu a uvolněn mimo viditelnost, tj. Create a Free nejsou třeba.
Použití může být již uvedené ukládání a načítání objektů do DB nebo třeba kdykoliv, kdy se vyplatí napsat obecný přístup pro práci s daty, které mají něco společného.
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Rtti;
type
// definice atrubutu
MyAttribute = class(TCustomAttribute)
private
FFieldName: string;
FSize: Integer;
public
constructor Create(const sFieldName : string; iSize : Integer);
property FieldName : string read FFieldName write FFieldName;
property Size : Integer read FSize write FSize;
end;
// nase trida
TMyClass = class
private
FFirstName:string;
FSurname: string;
public
[MyAttribute('dcFirstName', 50)] // v podstate volaní constructoru MyAttribute.Create
property FirstName:string read FFirstName write FFirstName;
[MyAttribute('dcSurnName', 30)]
property SurName: string read FSurname write FSurname;
end;
constructor MyAttribute.Create(const sFieldName: string; iSize: Integer);
begin
FSize := iSize;
FFieldName := sFieldName;
end;
// zde se pracuje s RTTI
procedure Test;
var
ctx : TRttiContext;
t : TRttiType;
m : TRttiProperty;
a : TCustomAttribute;
begin
ctx := TRttiContext.Create;
try
t := ctx.GetType(TMyClass);
for m in t.GetProperties do
for a in m.GetAttributes do
if a is MyAttribute then // ať se nám tam neplete nic co není naše
writeln(Format('Property = %s; Attribute = %s, FieldName = %s, Size = %d',
[m.Name, a.ClassName, MyAttribute(a).FieldName,
MyAttribute(a).Size]));
finally
ctx.Free;
end;
end;
begin
Test;
end.
Výsledek je pak:
Property = FirstName; Attribute = MyAttribute, FieldName = dcFirstName, Size = 50
Property = SurName; Attribute = MyAttribute, FieldName = dcSurnName, Size = 30
Samozřejmě každá sranda něco stojí a proto jsou EXE díky RTTI trošku větší (ale ne zas tak moc). Pokud použijete {$WEAKLINKRTTI ON} nebo dcc32 –weaklinkrtti popř. na začátku každého unitu (lépe přes include)
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
tak výsledná velikost EXE bude menší (ale zase nebudete moci použít nové RTTI).
A poslední poznámka: TRttiMethod umožňuje volání metod objektu za běhu:
TRttiMethod = class(TRttiMember)
//…
function Invoke(Instance: TObject; const Args: array of TValue): TValue; overload;
function Invoke(Instance: TClass; const Args: array of TValue): TValue; overload;
function Invoke(Instance: TValue; const Args: array of TValue): TValue; overload;
function GetParameters: TArray<TRttiParameter>; virtual; abstract;
function ToString: string; override;
property ReturnType: TRttiType read GetReturnType;
end;