How can I use an interface method as a parameter of method type in Delphi 10.2

Viewed 1092

I have one class instance and two references to it. There is one object reference (aMII : TMyInterfaceImpl) and one interface reference (iMI : IMyInterface). When I use the object reference to assign one of its methods to a method type variable (methodRef : TMyMethodRef), it works fine.

But if I want to use the interface reference in the same situation, the compiler throws an error:

[dcc32 Error] Unit1.pas(66): E2010 Incompatible types: 'TMyMethodRef' and 'procedure, untyped pointer or untyped parameter'

The minimal example:

type
  TMyMethodRef = procedure of object;
  //TMyMethodRef = procedure of interface; // Invalid definition, just a desperate attempt

  IMyInterface = interface
    ['{BEA60A2B-C20F-4E02-A938-65DD4332ADB0}']
    procedure foo;
  end;

  TMyInterfaceImpl = class ( TInterfacedObject, IMyInterface )
    procedure foo;
  end;

procedure TMyInterfaceImpl.foo;
begin
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  aMII : TMyInterfaceImpl;
  iMI : IMyInterface;
  methodRef : TMyMethodRef;
begin
  aMII := TMyInterfaceImpl.Create;
  iMI := aMII;
  methodRef := aMII.foo; // It compiles
  methodRef := iMI.foo; // It does not compile

end;

In my original source I have to use interfaces (I don't have the object references, because of the abstract factory design pattern : function TMyFactory.createMyInterface : IMyInterface).

And I have to use one of its methods as a callback function on some events (passed as a parameter to the setter method : procedure TMyInterfaceImpl2.setOnEvent( onEvent_ : TMyMethodRef ).

How can I do it? How can I assign one of the methods of an interface reference to a method type variable?

I know there is a solution to implement one another interface to access the object reference, but it does not secure (I could not be sure all the time the class implements both interfaces) and I don't want this solution:

type
  TMyMethodRef = procedure of object;

  IMyInterface = interface
    ['{BEA60A2B-C20F-4E02-A938-65DD4332ADB0}']
    procedure foo;
  end;

  TMyInterfaceImpl = class;

  IInstanceAccessor<T> = interface
    ['{61E36FF2-A9D3-4B46-BAE8-217CBE7A1F18}']
    function getInstance : T;
  end;

  TMyInterfaceImpl = class ( TInterfacedObject, IMyInterface, IInstanceAccessor<TMyInterfaceImpl> )
    procedure foo;
    function getInstance : TMyInterfaceImpl;
  end;

procedure TMyInterfaceImpl.foo;
begin
end;

function TMyInterfaceImpl.getInstance : TMyInterfaceImpl;
begin
  result := self;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  iMI : IMyInterface;
  aMII : TMyInterfaceImpl;
  methodRef : TMyMethodRef;
begin
  iMI := TMyInterfaceImpl.Create;
  aMII := ( iMI as IInstanceAccessor<TMyInterfaceImpl> ).getInstance;
  methodRef := aMII.foo;
end;
2 Answers

You cannot do that.

What you can do is declare an adapter class with such a method and use that method as the callback.

type
  TMyInterfaceAdapter = class
  private
    FIntf: IMyInterface;
  public
    procedure Foo;
    property Intf: IMyInterface read FIntf write FIntf;
  end;

procedure TMyInterface.Foo;
begin
  Assert(Assigned(FIntf), 'no interface assigned');
  FIntf.Foo;
end;

If you do not wish to maintain adapter classes and to manage their life-times, and are free to change to TMyMethodRef = System.SysUtils.TProc, you may also use anonymous methods:

var
  Impl: TMyInterfaceImpl;
  Intf: IMyInterface;
  MethodRef: TMyMethodRef;
begin
  Impl := TMyInterfaceImpl.Create;
  MethodRef := Impl.Foo;
  MethodRef;
  Intf := Impl; // Be sure to capture it!
  MethodRef := procedure begin Intf.foo; end;
  MethodRef;
end;

This works in the background somewhat similar to what you described in your own comment. Also, generally and as you are already using interfaces, if possible switch to consistently using interfaces.

Related