Home > other >  Delphi calls the incorrect function of my generic interface, when calling the function on a referenc
Delphi calls the incorrect function of my generic interface, when calling the function on a referenc

Time:01-04

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 HandleDogRequestand 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:

Generic interfaces and GUID

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.
  •  Tags:  
  • Related