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.