I have an object that implements multiple versions of a generic interface. To avoid confusion, I use method resolution clauses in my class: https://docwiki.embarcadero.com/RADStudio/Sydney/en/Implementing_Interfaces
Say I have a multi animal handler, that handles both cats and dogs (whatever that means). The handler implements a handler (IRequestHandler<TResponse, TRequest>
) interface for both the cats and dogs:
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;
Problem is, when I try to typecast to the specific interfaces IRequestHandler<TCatResponse, TCatRequest>
and IRequestHandler<TDogResponse, TDogRequest>
, it doesn't always resolve to the correct method.
Full Example:
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.
The troubling line is this:
(multiAnimalHandler as IRequestHandler<TCatResponse, TCatRequest>).Handle(catRequest);
The code will call HandleDogRequest
and not HandleCatRequest
, which I expected.
The above code will produce the following output:
Woof!
Miav!
Woof!
Woof!
What am I doing wrong or missing?
Thanks!
Edit: Work Around
In my specific use case, I could typecast my object to the concrete implementation. Not as beautiful code, but it works.
IRequestHandler<TCatResponse, TCatRequest>(invokable).Handle(catRequest)
CodePudding user response:
Your generic interface has a guid assigned to it, so all concrete implementations of the generic interface will share the same guid, which is not a good thing when it comes to typecasting interfaces using the as
operator. The lookup will only work for the first instance of the guid found.
Every distinct interface needs a unique guid. That is why your code doesn't work correctly. This is a long-standing problem going back over a decade:
Also related:
Delphi - how to use Supports with a generic interface GUID?
You are going to have to introduce some helpers, eg:
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.