使用 RTTI 在 Nullable<T> 记录中设置一个值
Setting a value in a Nullable<T> record with RTTI
我正在使用来自 Paolo Rossi 的 NEON 库与 Serialization/Deserialization 合作。
我正在尝试使用 RTTI 填充此 class,并使用我从数据库中获取的数据。
class 上的属性与数据库中的字段同名。
在图书馆里,我有这个 Nullable Record :
unit Neon.Core.Nullables;
interface
uses
System.SysUtils, System.Variants, System.Classes, System.Generics.Defaults, System.Rtti,
System.TypInfo, System.JSON;
type
ENullableException = class(Exception);
{$RTTI EXPLICIT FIELDS([vcPrivate]) METHODS([vcPrivate])}
Nullable<T> = record
private
FValue: T;
FHasValue: string;
procedure Clear;
function GetValueType: PTypeInfo;
function GetValue: T;
procedure SetValue(const AValue: T);
function GetHasValue: Boolean;
public
constructor Create(const Value: T); overload;
constructor Create(const Value: Variant); overload;
function Equals(const Value: Nullable<T>): Boolean;
function GetValueOrDefault: T; overload;
function GetValueOrDefault(const Default: T): T; overload;
property HasValue: Boolean read GetHasValue;
function IsNull: Boolean;
property Value: T read GetValue;
class operator Implicit(const Value: Nullable<T>): T;
class operator Implicit(const Value: Nullable<T>): Variant;
class operator Implicit(const Value: Pointer): Nullable<T>;
class operator Implicit(const Value: T): Nullable<T>;
class operator Implicit(const Value: Variant): Nullable<T>;
class operator Equal(const Left, Right: Nullable<T>): Boolean;
class operator NotEqual(const Left, Right: Nullable<T>): Boolean;
end;
NullString = Nullable<string>;
NullBoolean = Nullable<Boolean>;
NullInteger = Nullable<Integer>;
NullInt64 = Nullable<Int64>;
NullDouble = Nullable<Double>;
NullDateTime = Nullable<TDateTime>;
implementation
uses
Neon.Core.Utils;
{ Nullable<T> }
constructor Nullable<T>.Create(const Value: T);
var
a: TValue;
begin
FValue := Value;
FHasValue := DefaultTrueBoolStr;
end;
constructor Nullable<T>.Create(const Value: Variant);
begin
if not VarIsNull(Value) and not VarIsEmpty(Value) then
Create(TValue.FromVariant(Value).AsType<T>)
else
Clear;
end;
procedure Nullable<T>.Clear;
begin
FValue := Default(T);
FHasValue := '';
end;
function Nullable<T>.Equals(const Value: Nullable<T>): Boolean;
begin
if HasValue and Value.HasValue then
Result := TEqualityComparer<T>.Default.Equals(Self.Value, Value.Value)
else
Result := HasValue = Value.HasValue;
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> '';
end;
function Nullable<T>.GetValueType: PTypeInfo;
begin
Result := TypeInfo(T);
end;
function Nullable<T>.GetValue: T;
begin
if not HasValue then
raise ENullableException.Create('Nullable type has no value');
Result := FValue;
end;
function Nullable<T>.GetValueOrDefault(const Default: T): T;
begin
if HasValue then
Result := FValue
else
Result := Default;
end;
function Nullable<T>.GetValueOrDefault: T;
begin
Result := GetValueOrDefault(Default(T));
end;
class operator Nullable<T>.Implicit(const Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
class operator Nullable<T>.Implicit(const Value: Nullable<T>): Variant;
begin
if Value.HasValue then
Result := TValue.From<T>(Value.Value).AsVariant
else
Result := Null;
end;
class operator Nullable<T>.Implicit(const Value: Pointer): Nullable<T>;
begin
if Value = nil then
Result.Clear
else
Result := Nullable<T>.Create(T(Value^));
end;
class operator Nullable<T>.Implicit(const Value: T): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
class operator Nullable<T>.Implicit(const Value: Variant): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
function Nullable<T>.IsNull: Boolean;
begin
Result := FHasValue = '';
end;
class operator Nullable<T>.Equal(const Left, Right: Nullable<T>): Boolean;
begin
Result := Left.Equals(Right);
end;
class operator Nullable<T>.NotEqual(const Left, Right: Nullable<T>): Boolean;
begin
Result := not Left.Equals(Right);
end;
procedure Nullable<T>.SetValue(const AValue: T);
begin
FValue := AValue;
FHasValue := DefaultTrueBoolStr;
end;
end.
这是模型 class :
type
TMyClass = class(TPersistent)
private
FMyIntegerProp: Nullable<Integer>;
procedure SetMyIntegerProp(const Value: Nullable<Integer>);
published
Property MyIntegerProp: Nullable<Integer> read FMyIntegerProp write SetMyIntegerProp;
end;
implementation
{ TMyClass }
procedure TMyClass.SetMyIntegerProp(const Value: Nullable<Integer>);
begin
FMyIntegerProp := Value;
end;
到目前为止我的代码:
procedure DatasetToObject(AObject: TObject; AQuery: TFDQuery);
var
n: Integer;
LRttiContext: TRttiContext;
LRttiType: TRttiType;
LRttiProperty: TRttiProperty;
LFieldName: string;
Value: TValue;
LValue: TValue;
LRttiMethod : TRttiMethod;
begin
LRttiContext := TRttiContext.Create;
try
LRttiType := LRttiContext.GetType(AObject.ClassType);
for n := 0 to AQuery.FieldCount - 1 do
begin
LRttiProperty := LRttiType.GetProperty(AQuery.Fields[n].FieldName);
if (LRttiProperty <> nil) and (LRttiProperty.PropertyType.TypeKind = tkRecord) then
begin
LValue := LRttiProperty.GetValue(AObject);
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
if (LRttiProperty.PropertyType.Name = 'Nullable<System.Integer>') then
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]).AsInteger;
end;
end;
finally
LRttiContext.Free;
end;
end;
但到目前为止没有成功,我们将不胜感激。
Nullable.SetValue()
没有 return 值,但是当您在 TRttiMethod.Invoke()
TValue
上调用 AsInteger
时,您正试图读取一个值 returns。这将导致在运行时引发异常。
此外,当您读取 TMyClass.MyIntegerProp
属性 的值时,您最终会得到其 Nullable
记录的 copy ,因此 Invoke()
在该副本上 SetValue()
不会更新 MyIntegerProp
属性。之后您必须将修改后的 Nullable
分配回 MyIntegerProp
,例如:
LValue := LRttiProperty.GetValue(AObject);
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]);
LRttiProperty.SetValue(AObject, LValue); // <-- add this!
也就是说,Nullable.Value
属性 是只读的,但是 Nullable
有一个 SetValue()
方法,所以我建议更改 Value
属性改为读写,例如:
property Value: T read GetValue write SetValue;
然后你可以通过 RTTI 设置 Value
属性 而不是 Invoke()
直接使用 SetValue()
方法:
var
...
//LRttiMethod: TRttiMethod;
LRttiValueProp: TRttiProperty;
...
...
LRttiProperty := LRttiType.GetProperty(AQuery.Fields[n].FieldName);
if (LRttiProperty <> nil) and
(LRttiProperty.PropertyType.TypeKind = tkRecord) and
(LRttiProperty.PropertyType.Name = 'Nullable<System.Integer>') then
begin
LValue := LRttiProperty.GetValue(AObject);
{
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]);
}
LRttiValueProp := LRttiContext.GetType(LValue.TypeInfo).GetProperty('Value');
LRttiValueProp.SetValue(LValue.GetReferenceToRawData, AQuery.Fields[n].AsInteger);
LRttiProperty.SetValue(AObject, LValue);
end;
我正在使用来自 Paolo Rossi 的 NEON 库与 Serialization/Deserialization 合作。
我正在尝试使用 RTTI 填充此 class,并使用我从数据库中获取的数据。 class 上的属性与数据库中的字段同名。
在图书馆里,我有这个 Nullable Record :
unit Neon.Core.Nullables;
interface
uses
System.SysUtils, System.Variants, System.Classes, System.Generics.Defaults, System.Rtti,
System.TypInfo, System.JSON;
type
ENullableException = class(Exception);
{$RTTI EXPLICIT FIELDS([vcPrivate]) METHODS([vcPrivate])}
Nullable<T> = record
private
FValue: T;
FHasValue: string;
procedure Clear;
function GetValueType: PTypeInfo;
function GetValue: T;
procedure SetValue(const AValue: T);
function GetHasValue: Boolean;
public
constructor Create(const Value: T); overload;
constructor Create(const Value: Variant); overload;
function Equals(const Value: Nullable<T>): Boolean;
function GetValueOrDefault: T; overload;
function GetValueOrDefault(const Default: T): T; overload;
property HasValue: Boolean read GetHasValue;
function IsNull: Boolean;
property Value: T read GetValue;
class operator Implicit(const Value: Nullable<T>): T;
class operator Implicit(const Value: Nullable<T>): Variant;
class operator Implicit(const Value: Pointer): Nullable<T>;
class operator Implicit(const Value: T): Nullable<T>;
class operator Implicit(const Value: Variant): Nullable<T>;
class operator Equal(const Left, Right: Nullable<T>): Boolean;
class operator NotEqual(const Left, Right: Nullable<T>): Boolean;
end;
NullString = Nullable<string>;
NullBoolean = Nullable<Boolean>;
NullInteger = Nullable<Integer>;
NullInt64 = Nullable<Int64>;
NullDouble = Nullable<Double>;
NullDateTime = Nullable<TDateTime>;
implementation
uses
Neon.Core.Utils;
{ Nullable<T> }
constructor Nullable<T>.Create(const Value: T);
var
a: TValue;
begin
FValue := Value;
FHasValue := DefaultTrueBoolStr;
end;
constructor Nullable<T>.Create(const Value: Variant);
begin
if not VarIsNull(Value) and not VarIsEmpty(Value) then
Create(TValue.FromVariant(Value).AsType<T>)
else
Clear;
end;
procedure Nullable<T>.Clear;
begin
FValue := Default(T);
FHasValue := '';
end;
function Nullable<T>.Equals(const Value: Nullable<T>): Boolean;
begin
if HasValue and Value.HasValue then
Result := TEqualityComparer<T>.Default.Equals(Self.Value, Value.Value)
else
Result := HasValue = Value.HasValue;
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> '';
end;
function Nullable<T>.GetValueType: PTypeInfo;
begin
Result := TypeInfo(T);
end;
function Nullable<T>.GetValue: T;
begin
if not HasValue then
raise ENullableException.Create('Nullable type has no value');
Result := FValue;
end;
function Nullable<T>.GetValueOrDefault(const Default: T): T;
begin
if HasValue then
Result := FValue
else
Result := Default;
end;
function Nullable<T>.GetValueOrDefault: T;
begin
Result := GetValueOrDefault(Default(T));
end;
class operator Nullable<T>.Implicit(const Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
class operator Nullable<T>.Implicit(const Value: Nullable<T>): Variant;
begin
if Value.HasValue then
Result := TValue.From<T>(Value.Value).AsVariant
else
Result := Null;
end;
class operator Nullable<T>.Implicit(const Value: Pointer): Nullable<T>;
begin
if Value = nil then
Result.Clear
else
Result := Nullable<T>.Create(T(Value^));
end;
class operator Nullable<T>.Implicit(const Value: T): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
class operator Nullable<T>.Implicit(const Value: Variant): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
function Nullable<T>.IsNull: Boolean;
begin
Result := FHasValue = '';
end;
class operator Nullable<T>.Equal(const Left, Right: Nullable<T>): Boolean;
begin
Result := Left.Equals(Right);
end;
class operator Nullable<T>.NotEqual(const Left, Right: Nullable<T>): Boolean;
begin
Result := not Left.Equals(Right);
end;
procedure Nullable<T>.SetValue(const AValue: T);
begin
FValue := AValue;
FHasValue := DefaultTrueBoolStr;
end;
end.
这是模型 class :
type
TMyClass = class(TPersistent)
private
FMyIntegerProp: Nullable<Integer>;
procedure SetMyIntegerProp(const Value: Nullable<Integer>);
published
Property MyIntegerProp: Nullable<Integer> read FMyIntegerProp write SetMyIntegerProp;
end;
implementation
{ TMyClass }
procedure TMyClass.SetMyIntegerProp(const Value: Nullable<Integer>);
begin
FMyIntegerProp := Value;
end;
到目前为止我的代码:
procedure DatasetToObject(AObject: TObject; AQuery: TFDQuery);
var
n: Integer;
LRttiContext: TRttiContext;
LRttiType: TRttiType;
LRttiProperty: TRttiProperty;
LFieldName: string;
Value: TValue;
LValue: TValue;
LRttiMethod : TRttiMethod;
begin
LRttiContext := TRttiContext.Create;
try
LRttiType := LRttiContext.GetType(AObject.ClassType);
for n := 0 to AQuery.FieldCount - 1 do
begin
LRttiProperty := LRttiType.GetProperty(AQuery.Fields[n].FieldName);
if (LRttiProperty <> nil) and (LRttiProperty.PropertyType.TypeKind = tkRecord) then
begin
LValue := LRttiProperty.GetValue(AObject);
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
if (LRttiProperty.PropertyType.Name = 'Nullable<System.Integer>') then
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]).AsInteger;
end;
end;
finally
LRttiContext.Free;
end;
end;
但到目前为止没有成功,我们将不胜感激。
Nullable.SetValue()
没有 return 值,但是当您在 TRttiMethod.Invoke()
TValue
上调用 AsInteger
时,您正试图读取一个值 returns。这将导致在运行时引发异常。
此外,当您读取 TMyClass.MyIntegerProp
属性 的值时,您最终会得到其 Nullable
记录的 copy ,因此 Invoke()
在该副本上 SetValue()
不会更新 MyIntegerProp
属性。之后您必须将修改后的 Nullable
分配回 MyIntegerProp
,例如:
LValue := LRttiProperty.GetValue(AObject);
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]);
LRttiProperty.SetValue(AObject, LValue); // <-- add this!
也就是说,Nullable.Value
属性 是只读的,但是 Nullable
有一个 SetValue()
方法,所以我建议更改 Value
属性改为读写,例如:
property Value: T read GetValue write SetValue;
然后你可以通过 RTTI 设置 Value
属性 而不是 Invoke()
直接使用 SetValue()
方法:
var
...
//LRttiMethod: TRttiMethod;
LRttiValueProp: TRttiProperty;
...
...
LRttiProperty := LRttiType.GetProperty(AQuery.Fields[n].FieldName);
if (LRttiProperty <> nil) and
(LRttiProperty.PropertyType.TypeKind = tkRecord) and
(LRttiProperty.PropertyType.Name = 'Nullable<System.Integer>') then
begin
LValue := LRttiProperty.GetValue(AObject);
{
LRttiMethod := LRttiContext.GetType(LValue.TypeInfo).GetMethod('SetValue');
LRttiMethod.Invoke(LValue, [AQuery.Fields[n].AsInteger]);
}
LRttiValueProp := LRttiContext.GetType(LValue.TypeInfo).GetProperty('Value');
LRttiValueProp.SetValue(LValue.GetReferenceToRawData, AQuery.Fields[n].AsInteger);
LRttiProperty.SetValue(AObject, LValue);
end;