Delphi 将接口的属性作为泛型类型访问
Delphi Accessing properties of an interface as a generic type
我正在尝试通过通用 class 中的接口访问底层对象的属性,该通用 class 可以采用任何类型,即对参数没有约束。我可以为 class 参数成功执行此操作,但当参数是接口时遇到问题。通过阅读这里的许多帖子,我得出的结论是,访问接口的 RTTI 数据的唯一方法是转换回 TObject(假设它来自一个在我的上下文中是安全的对象)。
我的想法是使用支持来提取接口,然后将其转换回 TObject,然后测试 RTTI 以获取 属性 信息。我的问题是支持函数正在寻找 TObject、IInterface 或 TClass 的实例,如果没有参数约束,我无法从通用类型 T 中获取这些实例,这违背了包含此方法的 class 的目的。我生成了以下代码,展示了我对解决方案的尝试。
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.RTTI,
System.Typinfo;
Type
ITestIntf = interface(IInvokable)
['{61E0E2FC-59FA-4300-BE84-068BC265376E}']
function GetData: string;
procedure SetData(const Value: string);
property Data : string read GetData write SetData;
end;
TTestClass=class(TInterfacedObject, ITestIntf)
private
FData : string;
function GetData: string;
procedure SetData(const Value: string);
public
property Data : string read GetData write SetData;
end;
TAccess<T> = class
Function CheckProperty(AInstance : T; APropName : string) : boolean;
end;
var
LAccIntf : TAccess<ITestIntf>;
LAccClass : TAccess<TTestClass>;
LTestCls : TTestClass;
LTestIntf : ITestIntf;
{ TTestClass }
function TTestClass.GetData: string;
begin
Result := FData;
end;
procedure TTestClass.SetData(const Value: string);
begin
FData := Value;
end;
{ TAccess<T> }
function TAccess<T>.CheckProperty(AInstance : T; APropName : string) : boolean;
var
LType, LInstType : TRTTIType;
LIntf : IInterface;
LGUID : TGUID;
LContext : TRttiContext;
LInstance : TObject;
begin
Result := false;
LType := LContext.GetType(Typeinfo(T));
if LType.TypeKind = tkInterface then
begin
LGUID := GetTypeData(TypeInfo(T))^.Guid;
If Supports(AInstance, LGUID, LIntf) then // <- Error here
begin
LInstance := LIntf as TObject;
LInstType := LContext.GetType(LInstance.ClassType);
Result := (LInstType.GetProperty(APropName) <> nil);
end;
end else
if LType.TypeKind = tkClass then
begin
Result := (LType.GetProperty(APropName) <> nil);
end;
end;
begin
try
LTestCls := TTestClass.create;
LTestIntf := TTestClass.create;
LAccClass := TAccess<TTestClass>.Create;
if LAccClass.CheckProperty(LTestCls,'Data') then
writeln('Class Success!')
else
writeln('Class Failed');
LAccIntf := TAccess<ITestIntf>.Create;
if LAccIntf.CheckProperty(LTestIntf, 'Data') then
writeln('Intf Success!')
else
writeln('Intf Failed');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
编译时出现错误:
[dcc32 Error] Project1.dpr(68): E2250 There is no overloaded version of 'Supports' that can be called with these arguments
我不确定这是否可行,或者如果可行,我的做法是否正确?
谢谢
您的编译器反对此代码,因为它 AInstance
在编译时未被限制为接口变量。您的代码会在 运行 时对其进行测试,因此可以安全地转换 AInstance
.
有多种方法可以做到这一点。我可能会这样做:
Supports(PInterface(@AInstance)^, LGUID, LIntf)
其中
PInterface = ^IInterface
请注意,虽然这将使您的代码编译并 运行 如您所愿,但它实际上并没有测试接口是否实现了 属性。例如,如果您从实现 class 定义中删除 Data
属性,那么接口确实实现了 Data
属性,但是您的函数 returns False
.
我正在尝试通过通用 class 中的接口访问底层对象的属性,该通用 class 可以采用任何类型,即对参数没有约束。我可以为 class 参数成功执行此操作,但当参数是接口时遇到问题。通过阅读这里的许多帖子,我得出的结论是,访问接口的 RTTI 数据的唯一方法是转换回 TObject(假设它来自一个在我的上下文中是安全的对象)。
我的想法是使用支持来提取接口,然后将其转换回 TObject,然后测试 RTTI 以获取 属性 信息。我的问题是支持函数正在寻找 TObject、IInterface 或 TClass 的实例,如果没有参数约束,我无法从通用类型 T 中获取这些实例,这违背了包含此方法的 class 的目的。我生成了以下代码,展示了我对解决方案的尝试。
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.RTTI,
System.Typinfo;
Type
ITestIntf = interface(IInvokable)
['{61E0E2FC-59FA-4300-BE84-068BC265376E}']
function GetData: string;
procedure SetData(const Value: string);
property Data : string read GetData write SetData;
end;
TTestClass=class(TInterfacedObject, ITestIntf)
private
FData : string;
function GetData: string;
procedure SetData(const Value: string);
public
property Data : string read GetData write SetData;
end;
TAccess<T> = class
Function CheckProperty(AInstance : T; APropName : string) : boolean;
end;
var
LAccIntf : TAccess<ITestIntf>;
LAccClass : TAccess<TTestClass>;
LTestCls : TTestClass;
LTestIntf : ITestIntf;
{ TTestClass }
function TTestClass.GetData: string;
begin
Result := FData;
end;
procedure TTestClass.SetData(const Value: string);
begin
FData := Value;
end;
{ TAccess<T> }
function TAccess<T>.CheckProperty(AInstance : T; APropName : string) : boolean;
var
LType, LInstType : TRTTIType;
LIntf : IInterface;
LGUID : TGUID;
LContext : TRttiContext;
LInstance : TObject;
begin
Result := false;
LType := LContext.GetType(Typeinfo(T));
if LType.TypeKind = tkInterface then
begin
LGUID := GetTypeData(TypeInfo(T))^.Guid;
If Supports(AInstance, LGUID, LIntf) then // <- Error here
begin
LInstance := LIntf as TObject;
LInstType := LContext.GetType(LInstance.ClassType);
Result := (LInstType.GetProperty(APropName) <> nil);
end;
end else
if LType.TypeKind = tkClass then
begin
Result := (LType.GetProperty(APropName) <> nil);
end;
end;
begin
try
LTestCls := TTestClass.create;
LTestIntf := TTestClass.create;
LAccClass := TAccess<TTestClass>.Create;
if LAccClass.CheckProperty(LTestCls,'Data') then
writeln('Class Success!')
else
writeln('Class Failed');
LAccIntf := TAccess<ITestIntf>.Create;
if LAccIntf.CheckProperty(LTestIntf, 'Data') then
writeln('Intf Success!')
else
writeln('Intf Failed');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
编译时出现错误:
[dcc32 Error] Project1.dpr(68): E2250 There is no overloaded version of 'Supports' that can be called with these arguments
我不确定这是否可行,或者如果可行,我的做法是否正确?
谢谢
您的编译器反对此代码,因为它 AInstance
在编译时未被限制为接口变量。您的代码会在 运行 时对其进行测试,因此可以安全地转换 AInstance
.
有多种方法可以做到这一点。我可能会这样做:
Supports(PInterface(@AInstance)^, LGUID, LIntf)
其中
PInterface = ^IInterface
请注意,虽然这将使您的代码编译并 运行 如您所愿,但它实际上并没有测试接口是否实现了 属性。例如,如果您从实现 class 定义中删除 Data
属性,那么接口确实实现了 Data
属性,但是您的函数 returns False
.