Как проверить, ссылаются ли две ссылки на метод на один и тот же метод?

Я пытаюсь сделать список обработчиков событий, где обработчик-это эталонный метод. Чтобы удалить конкретный обработчик, мне нужно найти его в списке. Но как я могу сравнить адрес кода двух ссылок на метод?

type
  TEventHandler = reference to procedure;

procedure TestProc;
begin
end;

procedure TForm26.FormCreate(Sender: TObject);
var
  Handlers: TList<TEventHandler>;
begin
  Handlers := TList<TEventHandler>.create;
  try
    Handlers.Add(TestProc);
    Handlers.Remove(TestProc); { doesn't work }
    Assert(Handlers.Count=0);  { fails }
    Assert(Handlers.IndexOf(TestProc)>=0); { fails }
  finally
    FreeAndNil(Handlers);
  end;
end;

default comparer of TList не сравнивает ссылки на методы должным образом. Как я могу их сравнить? Есть ли структура, подобная TMethod, но для ссылок на методы?

1 ответов


Это не так просто, как может показаться.

чтобы понять, почему это происходит, вам нужно понять, как назначение ссылки на метод выполняется компилятором.

код, который вы написали, в основном переведен в это компилятором:

Handlers.Add(procedure begin TestProc; end);
Handlers.Remove(procedure begin TestProc; end);

теперь мы должны знать, что если у вас есть несколько анонимных методов в одной и той же процедуре, они на самом деле разные анонимные методы, даже если их код идентичен. (см. как анонимный методы, реализованные под капотом?)

Это означает, что значения, которые передают Add и Remove отличаются, даже если код в их телах одинаковый - даже при взломе вокруг него потребуется анализ двоичного кода, чтобы определить, является ли код внутри тела одинаковым.

если вы измените код следующим образом, он будет работать, потому что тогда у вас есть только один анонимный метод - для этого он работает, но обычно вы не добавляете и удаляете внутри точно такая же процедура:

var
  Handlers: TList<TEventHandler>;
  Handler: TEventHandler;
begin
  Handlers := TList<TEventHandler>.create;
  try
    Handler := TestProc;
    Handlers.Add(Handler);
    Handlers.Remove(Handler);
    Assert(Handlers.Count=0);
  finally
    FreeAndNil(Handlers);
  end;
end;

Если вам нужен список, в котором вы добавляете и удаляете обработчики событий, моя личная рекомендация-избегать анонимного типа метода и использовать процедуру или методы:

type
  TEventHandlerA = procedure;
  TEventHandlerB = procedure of object;

решение, какой из них лучше, зависит от вас, потому что вы лучше знаете свой код.