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

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

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;

Сравнение по умолчанию 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;

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