如何在兼容的方法指针之间转换?

How to cast between compatible method pointers?

我有两个不同的方法指针。

type
  TComponentMethod = procedure(const AComponent: TComponent) of object;
  TFormMethod = procedure(const AForm: TForm) of object;

唯一的区别是参数的类型,但两者都是对象引用,因此从调用约定的角度来看应该没有任何区别。

(但是这可能是一个类型安全问题,因为协变/逆变。)

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    procedure M2(const AForm: TForm);
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  FormMethod: TFormMethod;
  ComponentMethod: TComponentMethod;
begin
  FormMethod := M2;
  // How to cast this?
  ComponentMethod := M2;
end;

编译器不允许我这样做。

[dcc32 Error] Unit1.pas(32): E2010 Incompatible types: 'TComponent' and 'TForm'

有什么方法可以将一个方法指针类型转换为另一个 "compatible" 方法指针吗?

它们不兼容。

如果 M2TForm 作为其输入之一,编译器可以合理地期望在 M2 的主体中访问表单 methods/members。

一个TComponentMethod事件处理程序只需要一个TComponent实例来调用它。因此,这种组合(如果编译器允许的话)将使访问 TComponent 实例上的 TForm 成员成为可能。 (显然是灾难的根源。)

例如

procedure TForm1.M2(const AForm: TForm);
begin
  AForm.ModalResult := mrCancel;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  ComponentMethod: TComponentMethod;
begin
  ComponentMethod := M2;

  //The next line is legal. But if the previous line were legal,
  //you'd attempt to access TForm(AComponent).ModalResult ...
  //An AV if you're lucky, and weird behaviour if you're not.
  ComponentMethod(TComponent.Create(Self));
end;

也就是说,您可以执行硬类型转换。但是,TComponentMethod(M2) 将不起作用,因为编译器几乎在您使用标识符的任何时候都想调用 M2。首先,只有一点 "compiler magic" 允许 FormMethod := M2。因此,您需要一个中间事件处理程序变量来保存对 M2 的引用。而且因为事件处理程序变量不是函数,所以可以在不尝试调用它的情况下对其进行类型转换。

Temp := M2;
ComponentMethod := TComponentMethod(Temp);

警告 这是个糟糕的主意,但它确实有效:

  • 提示:ComponentMethod变量可以如下使用:ComponentMethod(AComponentThatsNotAForm).
  • 如果你能真正保证之前的调用是安全的,因为M2是如何实现的,那么你应该声明M2如下:procedure M2(const AComponent: TComponent);(如果它不需要表单实例来完成它的工作,就不要要求表单实例。)... 而且您不必费心费力地寻找一种方法来搬起石头砸自己的脚

你可以这样做:

var
  FormMethod: TFormMethod;
  ComponentMethod: TComponentMethod;
begin
  FormMethod := M2;
  ComponentMethod := TComponentMethod(FormMethod);
end;

据我所知,诀窍是您需要先分配一个临时局部变量,然后再将其分配给 ComponentMethod

如您所知,这不是类型安全的。如果调用 ComponentMethod 的参数不是从 TForm 派生的,那么编译器将无法拯救你。

大卫的回答很好。我已经编写了一个通用方法来避免每次转换时都必须声明源类型的临时变量。

type
  TOneArgMethod<T> = procedure(const A: T) of object;


function TForm1.CastMethod<TFrom, TTo>(AFrom: TOneArgMethod<TFrom>)
  : TOneArgMethod<TTo>;
var
  Method: TMethod;
begin
  Method := TMethod(AFrom);
  Result := TOneArgMethod<TTo>(Method);
end;