Delphi的TValue探索(一)

TValue是Delphi的RTTI系统的重要类型。 经过摸索,发现TValue功能强大,可以实现很多功能。本文章中所有程序采用XE3运行通过。

一、TValue结构

TValue定义在System.Rtti.pas

TValue = record



 ...

private

  FData: TValueData

end;

TValue提供了一些系列方法,几乎都是操作FData.

TValueData描述如下:

  TValueData = record

    FTypeInfo: PTypeInfo;

    // FValueData vs old FHeapData:

    // FHeapData doubled as storage for interfaces. However, that was ambiguous

    // in the case of nil interface values: FTypeInfo couldn't be trusted

    // because it looked like the structure was uninitialized. Then, DataSize

    // would be 0.

    // FValueData is different: interfaces are always stored like strings etc.,

    // as a reference stored in a blob on the heap.

    FValueData: IValueData;

    case Integer of

      0: (FAsUByte: Byte);

      1: (FAsUWord: Word);

      2: (FAsULong: LongWord);

      3: (FAsObject: Pointer);

      4: (FAsClass: TClass);

      5: (FAsSByte: Shortint);

      6: (FAsSWord: Smallint);

      7: (FAsSLong: Longint);

      8: (FAsSingle: Single);

      9: (FAsDouble: Double);

      10: (FAsExtended: Extended);

      11: (FAsComp: Comp);

      12: (FAsCurr: Currency);

      13: (FAsUInt64: UInt64);

      14: (FAsSInt64: Int64);

      15: (FAsMethod: TMethod);

      16: (FAsPointer: Pointer);

  end;

TValueData是一个结构体,TValueData可以存储任何类型的数据,经过TValue的方法可以与任何类型进行转换:

TValue = record

  ...

public

  ...

  // Low-level in

  class procedure Make(ABuffer: Pointer; ATypeInfo: PTypeInfo; out Result: TValue); overload; static;

  class procedure MakeWithoutCopy(ABuffer: Pointer; ATypeInfo: PTypeInfo; out Result: TValue); overload; static;

  class procedure Make(AValue: NativeInt; ATypeInfo: PTypeInfo; out Result: TValue); overload; static;



  // Low-level out

  property DataSize: Integer read GetDataSize;

  procedure ExtractRawData(ABuffer: Pointer);

  // If internal data is something with lifetime management, this copies a 

  // reference out *without* updating the reference count.

  procedure ExtractRawDataNoCopy(ABuffer: Pointer);

  function GetReferenceToRawData: Pointer;

  function GetReferenceToRawArrayElement(Index: Integer): Pointer;

  ...

end;

通过调用Make(...),将任意类型数据转换为TValue
通过调用ExtractRawData(...), ExtractRawDataNoCopy(...)将TValue转换为任意数据类型,两者区别是ExtractRawDataNoCopy转换时在堆中申请内存的数据,而ExtractRawData是安全的。
GetReferenceToRawData返回数据的指针,也是堆内存的指针。

二、类型转换为TValue

下面例子测试Integer和TRect:

 1 program Project1;

 2 {$APPTYPE CONSOLE}

 3 uses SysUtils, Windows, TypInfo,Rtti;

 4 

 5 var

 6   IntData : Integer;

 7   IntValue : TValue;

 8 

 9   RecData : TRect;

10   RecValue : TValue;

11 

12 begin

13   IntData := 1234;

14   TValue.Make(@IntData,TypeInfo(Integer),IntValue); //Integer类型也可以直接调用 IntValue := IntData;  这里演示TValue.Make

15   Writeln(IntValue.ToString);

16   RecData.Left := 10;

17   RecData.Right := 20;

18   TValue.Make(@RecData,TypeInfo(TRect),RecValue);

19   Writeln(RecValue.ToString);

20   readln;

21 end.
运行结果:

1234

(record)

三、TValue转换到类型

在反序列化(反持久化)时,如果知道数据类型,可以调用下面的方法生成一个与此类型相应的TValue空记录:

TValue.Make(nil,TypeInfoVar,OutputTValue);

通过ExtractRawData,可以将TValue数据直接转换某类型数据:

 1 program Project2;

 2 {$APPTYPE CONSOLE}

 3 uses SysUtils, Windows, TypInfo,Rtti;

 4 

 5 var

 6   RecData : TRect;

 7   RecDataOut : TRect;

 8   RecValue : TValue;

 9 

10 begin

11   RecData.Left := 10;

12   RecData.Right := 20;

13   TValue.Make(@RecData,TypeInfo(TRect),RecValue);    //将TRect结构的RecData转换为TValue类型的RecValue

14 

15   RecValue.ExtractRawData(@RecDataOut);    //将TValue 结构数据转换成TRect类型数据

16   Writeln(RecDataOut.Left);

17   Writeln(RecDataOut.Right);

18 

19   readln;

20 end.
运行结果

10

20

四、通过TValue的访问类型的成员变量

TValue转换自某个类型后,可以使用的GetReferenceToRawData()获取数据指针,通过调用SetValue和GetValue读写
某个成员的值。

 1 program Project3;

 2 {$APPTYPE CONSOLE}

 3 uses SysUtils, Windows, TypInfo,Rtti;

 4 

 5 var

 6   RecData : TRect;

 7   RecValue : TValue;

 8   Ctx : TRttiContext;

 9 

10 begin

11   Ctx := TRttiContext.Create;

12   // 创建空的、与TRect对应的TValue结构体,

13   TValue.Make(nil,TypeInfo(TRect),RecValue);

14   // 设置 Left 、 Right 成员变量值,使用TValue中成员变量的地址指针

15   Ctx.GetType(TypeInfo(TRect)).GetField('Left').SetValue(RecValue.GetReferenceToRawData,10);

16   Ctx.GetType(TypeInfo(TRect)).GetField('Right').SetValue(RecValue.GetReferenceToRawData,20);

17   // 转换为TRect结构体数据

18   RecValue.ExtractRawData(@RecData);

19   Writeln(RecData.Left);

20   Writeln(RecData.Right);

21   readln;

22   Ctx.Free;

23 end.
运行结果:



10

20

五、泛型转换函数

我们上面的例子,通过调用Make函数来转换成TValue,以及通过ExtractRawData转换成需要的类型,
Delphi还提供了泛型转换函数,可以指定已知的类型,直接进行转换:

class function From<T>(const Value: T): TValue; static;

function AsType<T>: T;

function IsType<T>: Boolean;

function TryAsType<T>(out AResult: T): Boolean;

function Cast<T>: TValue; overload;

看下面的例子:

 1 program Project4;

 2 {$APPTYPE CONSOLE}

 3 uses SysUtils, Windows, TypInfo,Rtti;

 4 

 5 var

 6   RecData : TRect;

 7   RecDataOut : TRect;

 8   RecValue : TValue;

 9 begin

10   RecData.Left := 10;

11   RecData.Right := 20;

12 

13   RecValue := TValue.From<TRect>(RecData);        //直接转换成 TValue

14 

15   Writeln(RecValue.IsType<TRect>);

16 

17   RecDataOut := RecValue.AsType<TRect>;            //TValue直接转换成TRect

18 

19   Writeln(RecDataOut.Left);

20   Writeln(RecDataOut.Right);

21   readln;

22 end.
运行结果:



TRUE

10

20

六、数组

如果TValue转换自数组类型,则可以调用一下方法:

function GetArrayLength: Integer;

function GetArrayElement(Index: Integer): TValue;

procedure SetArrayElement(Index: Integer; const AValue: TValue);

如果用下面的方式定义数组,则不支持转换到TValue:

var

  IntArray : array of Integer;

我可以先定义数组类型后,再定义变量,则可以转换到TValue:

type

  TIntArray = array of Integer;

var

  IntArray : TIntArray;

  // 或者

  IntArray : TArray<Integer>; //在 System.pas 定义:   TArray<T> = array of T;

七、Variant

Variant与TValue的转换容易产生混淆,调用TValue.FromVariant(),并不是将Varaint转换为TValue:

 1 program Project5;

 2 {$APPTYPE CONSOLE}

 3 uses SysUtils, TypInfo,Rtti;

 4 

 5 var

 6   vExample : Variant;

 7   Value : TValue;

 8 begin

 9   vExample := 'Hello World';

10   Value := TValue.FromVariant(vExample);    //

11   writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));

12   Writeln(value.ToString);

13 

14   vExample := 1234;

15   Value := TValue.FromVariant(vExample);

16   writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));

17   Writeln(value.ToString);

18 

19   readln;

20 end.
运行结果:



tkUString

Hello World

tkInteger 1234

如果希望将Variant转换为TValue,可以使用这个方法:

 1 program Project6;

 2 {$APPTYPE CONSOLE}

 3 uses SysUtils, TypInfo,Rtti;

 4 

 5 var

 6   vExample : Variant;

 7   Value : TValue;

 8 begin

 9   vExample := 'Hello World';

10   Value := TValue.From<variant>(vExample);

11   writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));

12   Writeln(value.AsType<variant>);

13 

14   vExample := 1234;

15   Value := TValue.From<variant>(vExample);

16   writeln(GetEnumName(TypeInfo(TTypeKind),Ord(Value.Kind)));

17   Writeln(value.AsType<variant>);

18 

19   readln;

20 end.
运行结果:



tkVariant

Hello World

tkVariant 1234

 

你可能感兴趣的:(Delphi)