Spring4d如何让IEvent句柄引用程序?

Spring4d How to make IEvent handle reference to procedure?

当实现具有以下定义的事件时,Spring4D 将添加和调用方法,但不会删除处理程序(使用 IEvent.Remove(MyProc) ),因为它无法识别它。

  {$M+}
  TaskItemChangeEvent = reference to procedure(const TaskItem: ITaskItem; Event: TTaskListEvent);

以下确实有效,但我不想被迫绑定到对象。

  {$M+}
  TaskItemChangeEvent = procedure(const TaskItem: ITaskItem; Event: TTaskListEvent) of Object;

我认为问题是 TEventBase.Remove 中的这一行作为对过程的引用不是 TMethod?

  if TMethod(handlers[i]) = TMethod(handler) then

原因是编译器可能会在您添加和删除它们的地方创建不同的匿名方法实例。

看下面的代码:

var
  proc: TProc;

procedure Add(p: TProc);
begin
  proc := p;
end;

procedure Remove(p: TProc);
begin
  Writeln(PPointer(@proc)^ = PPointer(@p)^);
end;

procedure A;
var
  p: TProc;
begin
  p := procedure begin end;
  Add(p);
  Remove(p);
end;

procedure B;
begin
  Add(procedure begin end);
  Remove(procedure begin end);
end;

procedure C;
begin
  Add(A);
  Remove(A);
end;

begin
  A;
  B;
  C;
  Readln;
end.

你会注意到在 BC 中它会打印 False 因为传递给 AddRemove 的两个匿名方法不同于彼此。虽然在 B 中很明显在 C 中不是,但编译器实际上将代码转换为:

procedure C;
begin
  Add(procedure begin A(); end);
  Remove(procedure begin A(); end);
end;

这意味着如果您想将 IEvent<> 与方法引用类型一起使用并且能够删除您需要保留您添加的引用以使它们相等从而能够被找到调用 Remove.

TEventBase 内部,引用都被处理为 TMethod 的事实与此无关 - 当传递匿名方法时,它被转换为 TMethod。毕竟匿名方法类型是一个由编译器创建的对象支持的接口,这使得进行此类转换成为可能,并导致必须保留添加的引用以将其删除。