Monday, September 14, 2009

Introduction to TValue

TValue is a new record structure defined in Rtti.pas, it provides the ability to store the value, and type information of an instance of any type. This can be seen in the following code.

For example, in the following program, you can see that the TypeInfo property on
a TValue is the same as the what the TypeInfo() function would return for that type.


program Project10;
{$APPTYPE CONSOLE}

uses
SysUtils, Rtti;

var
v : TValue;
i : Integer;

begin
i := 10;
v := I;
Writeln('Address of TypeInfo on TValue :', IntToStr(Integer(v.TypeInfo)));
Writeln('Address of TypeInfo on Integer:', IntToStr(Integer(TypeInfo(Integer))));
Writeln('Value of I:',I);
Writeln('Value of V:',v.AsInteger);
readln;
end.

Output:

Address of TypeInfo on TValue :4198560
Address of TypeInfo on Integer:4198560
Value of I:10
Value of V:10


There are several implicit operators defined that allow assignment of these types easy. If you have a type that is not on this list, don't worry there is a way to handle these, it just want to dedicate a single blog post to how to do this.


class operator Implicit(const Value: string): TValue;
class operator Implicit(Value: Integer): TValue;
class operator Implicit(Value: Extended): TValue;
class operator Implicit(Value: Int64): TValue;
class operator Implicit(Value: TObject): TValue;
class operator Implicit(Value: TClass): TValue;
class operator Implicit(Value: Boolean): TValue;


There are as set of matching functions to get the data out.


function AsString: string;
function AsInteger: Integer;
function AsExtended: Extended;
function AsInt64: Int64;
function AsObject: TObject;
function AsClass: TClass;
function AsBoolean: Boolean;


Although, this may make you think about the Variant type, conversions from one type to another do not automatically occur. The following code generates an
EInvalidCast Exception, as the stored type is an Integer and not a string.


program Project10;
{$APPTYPE CONSOLE}

uses
Rtti;

var
v : TValue;
i : Integer;

begin
i := 10;
v := I;
// Generates and Invalid Type Cast
Writeln('Value of V:',v.asString);
readln;
end.


To help you in determining what type is in a given TValue you have several properties and functions.


// in TypInfo.pas
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray, tkUString,
tkClassRef, tkPointer, tkProcedure);

// part of TValue in rtti.pas
property Kind: TTypeKind
function IsObject: Boolean;
function IsType<T>: Boolean; overload;



The following shows each of these:


program Project10;
{$APPTYPE CONSOLE}

uses
Rtti,TypInfo;

var
v : TValue;
i : Integer;

begin
i := 10;
v := I;
Writeln('V.Kind =',GetEnumName(TypeInfo(TTypeKind),ord(v.Kind)));
Writeln('V.IsType<Integer> = ',v.IsType<Integer>);
Writeln('V.IsType<TObject> = ',v.IsType<TObject>);
Writeln('V.IsObject = ',v.IsObject);
readln;
end.

Output:

V.Kind =tkInteger
V.IsType<Integer> = TRUE
V.IsType<TObject> = FALSE
V.IsObject = FALSE


TValue has a way more functionality to explore, that I will cover in an in depth article later, but the basics needed to be covered before
moving on to TRttiMember for Properties and Fields which is the next article.

RTTI Article List

1 comment:

  1. Hmm... can you put enums into TValue? 'Cause if you can, that would sure clean up our base DUnit test class. Currently it's got dozens of overloads of CheckEquals to handle all the different enums we want to write tests on.

    ReplyDelete