Delphi 在对具体接口类型的引用上调用函数时,调用了我的通用接口的错误函数

Delphi calls the incorrect function of my generic interface, when calling the function on a reference to the the concrete interface type

我有一个实现通用接口多个版本的对象。为避免混淆,我在 class 中使用了方法解析子句: https://docwiki.embarcadero.com/RADStudio/Sydney/en/Implementing_Interfaces

假设我有一个多动物处理程序,可以处理猫和狗(无论那是什么意思)。处理程序为猫和狗实现处理程序 (IRequestHandler<TResponse, TRequest>) 接口:

IRequestHandler<TResponse: record; TRequest: record> = Interface(IInvokable)
    ['{AFF8703B-F2AC-44D6-B82C-43E3492ADBB3}']
    function Handle(ARequest: TRequest): TResponse;
End;

TCatRequest = record
end;

TCatResponse = record
end;

TDogRequest = record
end;

TDogResponse = record
end;

TMultiAnimalHandler=class(TInterfacedObject,
                                                    IRequestHandler<TCatResponse, TCatRequest>,
                                                    IRequestHandler<TDogResponse, TDogRequest>)

public
    function IRequestHandler<TCatResponse, TCatRequest>.Handle = HandleCatRequest;
    function HandleCatRequest(ARequest: TCatRequest): TCatResponse;

    function IRequestHandler<TDogResponse, TDogRequest>.Handle = HandleDogRequest;
    function HandleDogRequest(ARequest: TDogRequest): TDogResponse;
end;

问题是,当我尝试对特定接口 IRequestHandler<TCatResponse, TCatRequest>IRequestHandler<TDogResponse, TDogRequest> 进行类型转换时,它并不总是解析为正确的方法。

完整示例:

type
    IRequestHandler<TResponse: record; TRequest: record> = Interface(IInvokable)
        ['{AFF8703B-F2AC-44D6-B82C-43E3492ADBB3}']
        function Handle(ARequest: TRequest): TResponse;
    End;

    TCatRequest = record
    end;

    TCatResponse = record
    end;

    TDogRequest = record
    end;

    TDogResponse = record
    end;

    TMultiAnimalHandler=class(TInterfacedObject,
                                                        IRequestHandler<TCatResponse, TCatRequest>,
                                                        IRequestHandler<TDogResponse, TDogRequest>)

    public
        function IRequestHandler<TCatResponse, TCatRequest>.Handle = HandleCatRequest;
        function HandleCatRequest(ARequest: TCatRequest): TCatResponse;

        function IRequestHandler<TDogResponse, TDogRequest>.Handle = HandleDogRequest;
        function HandleDogRequest(ARequest: TDogRequest): TDogResponse;
    end;

function TMultiAnimalHandler.HandleCatRequest(
    ARequest: TCatRequest): TCatResponse;
begin
    WriteLn('Miav!');
end;

function TMultiAnimalHandler.HandleDogRequest(
    ARequest: TDogRequest): TDogResponse;
begin
    WriteLn('Woof!');
end;

var
    catHandler: IRequestHandler<TCatResponse, TCatRequest>;
    dogHandler: IRequestHandler<TDogResponse, TDogRequest>;
    multiAnimalHandler: TMultiAnimalHandler;
    dogRequest: TDogRequest;
    catRequest: TCatRequest;
begin
    try
        multiAnimalHandler := TMultiAnimalHandler.Create;
        dogHandler := multiAnimalHandler;
        catHandler := multiAnimalHandler;

        // Works
        dogHandler.Handle(dogRequest);
        // Works
        catHandler.Handle(catRequest);
        // Works
        (multiAnimalHandler as IRequestHandler<TDogResponse, TDogRequest>).Handle(dogRequest);
        // Does not work. The Handle function calls HandleDogRequest, even if I cast multiAnimalHandler to IRequestHandler<TCatResponse, TCatRequest>!?
        (multiAnimalHandler as IRequestHandler<TCatResponse, TCatRequest>).Handle(catRequest);
    except
        on E: Exception do
            Writeln(E.ClassName, ': ', E.Message);
    end;
end.

令人不安的是:

(multiAnimalHandler as IRequestHandler<TCatResponse, TCatRequest>).Handle(catRequest);

该代码将调用 HandleDogRequest 而不是我预期的 HandleCatRequest

以上代码将产生以下输出:

Woof!
Miav!
Woof!
Woof!

我做错了什么或遗漏了什么?

谢谢!

编辑:解决方法

在我的特定用例中,我可以将我的对象强制转换为具体实现。没有那么漂亮的代码,但它有效。

IRequestHandler<TCatResponse, TCatRequest>(invokable).Handle(catRequest)

您的通用接口分配了一个 guid,因此通用接口的所有具体实现都将共享相同的 guid,这在使用 as 运算符对接口进行类型转换时不是一件好事。查找仅适用于找到的 guid 的第一个实例。

每个不同的界面都需要一个唯一的 GUID。这就是您的代码无法正常工作的原因。这是一个可以追溯到十年前的长期问题:

Generic interfaces and GUID

还相关:

Delphi - how to use Supports with a generic interface GUID?

你将不得不介绍一些助手,例如:

type
  IRequestHandler<TResponse: record; TRequest: record> = Interface(IInvokable)
    function Handle(ARequest: TRequest): TResponse;
  end;

  TCatRequest = record
  end;

  TCatResponse = record
  end;

  ICatRequestHandler = interface(IRequestHandler<TCatResponse, TCatRequest>)
    ['{5D2B15AC-B11D-49B4-8249-65D42596CEA9}']
  end;

  TDogRequest = record
  end;

  TDogResponse = record
  end;

  IDogRequestHandler = interface(IRequestHandler<TDogResponse, TDogRequest>)
    ['{9637C82D-97D3-4F82-B8B2-89CE22092438}']
  end;

  TMultiAnimalHandler = class(TInterfacedObject, ICatRequestHandler, IDogRequestHandler)
  public
    function ICatRequestHandler.Handle = HandleCatRequest;
    function HandleCatRequest(ARequest: TCatRequest): TCatResponse;

    function IDogRequestHandler.Handle = HandleDogRequest;
    function HandleDogRequest(ARequest: TDogRequest): TDogResponse;
  end;

...

var
  catHandler: ICatRequestHandler;
  dogHandler: IDogRequestHandler;
  multiAnimalHandler: TMultiAnimalHandler;
  dogRequest: TDogRequest;
  catRequest: TCatRequest;
begin
  try
    multiAnimalHandler := TMultiAnimalHandler.Create;
    dogHandler := multiAnimalHandler;
    catHandler := multiAnimalHandler;

    dogHandler.Handle(dogRequest);
    catHandler.Handle(catRequest);
    (multiAnimalHandler as IDogRequestHandler).Handle(dogRequest);
    (multiAnimalHandler as ICatRequestHandler).Handle(catRequest);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.