Что именно делает «Ссылка на» в Delphi?

#delphi #anonymous-types #delphi-10.4-sydney

#delphi #анонимные типы #delphi-10.4-сидней

Вопрос:

Читая документацию по анонимным методам в Delphi, я начал задаваться вопросом. Я всегда использовал что-то вроде этого:

 type TMathFn = Function(A, B: Integer): Integer;
var fn: TMathFn;
  

У меня всегда получалось. Но этот документ говорит мне использовать это вместо:

 type TMathFn = Reference to Function(A, B: Integer): Integer;
var fn: TMathFn;
  

И поскольку я разрабатывал в Delphi с 1994 по 2010 год, я немного незнаком с этой частью «Ссылки на». Тем не менее, оба варианта, похоже, работают одинаково. Итак…
Они идентичны?

Ответ №1:

«ССЫЛКА НА» предназначена для разрешения анонимных методов (встроенных определений процедур / функций), которые могут захватывать контекст (например, локальные переменные, которые записываются как ссылки, т. Е.. если вы измените переменную после захвата, будет записано измененное значение, см. Ниже).

 TYPE TMyProc = REFERENCE TO PROCEDURE(CONST S : STRING);

PROCEDURE Information(CONST S : STRING);
  BEGIN
    MessageDlg(S,mtInformation,[mbOK],0)
  END;

PROCEDURE RunProc(P : TMyProc ; CONST S : STRING);
  BEGIN
    P(S)
  END;

PROCEDURE A(B,C : INTEGER);
  VAR
    D : INTEGER;
    P : TMyProc;

  BEGIN
    D:=3;
    // D is 3 at the time of capture
    P:=PROCEDURE(CONST S : STRING)
         BEGIN
           Information(S ': ' IntToStr(D) ' -> ' IntToStr(B))
         END;
    // D is now 4 - and is reflected in the captured routine, as
    // the capture is done by REFERENCE and not by VALUE.
    INC(D);
    RunProc(P,'Hello')
  END;

BEGIN
  A(2,3)
END.
  

В окне сообщения отобразится «Привет: 4 -> 2».

Приведенное выше определение P «захватывает» (включает) переменные D и B, так что даже если вы передадите его другой функции, где эти переменные не существуют, вы все равно сможете получить к ним доступ.

Это было бы (почти) невозможно сделать с обычными процедурами [объектных] типов, поскольку они не могут получить доступ к локальным переменным, объявленным в момент выполнения.

Комментарии:

1. Для меня достаточно ясно. Поскольку мне просто нужны указатели на функции, я могу пропустить «ссылку на». Как я и подозревал. 🙂

2. Небольшая проблема. Захват не состояния, а переменных. Состояние обычно подразумевает, что фиксируются значения, но фиксируются переменные.

3. @DavidHeffernan: Хороший момент — я соответствующим образом обновил ответ…

4. Это чрезмерное (и смешанное) использование ВСЕХ заглавных букв заставляет меня рвать на себе волосы…

5. @JerryDodge: Тогда отвернись — мы не можем допустить, чтобы ты облысел 🙂

Ответ №2:

Нет, они не идентичны.

Разница в том, что

 TMathFn = function(A, B: Integer): Integer;
  

это обычная функция,

 TMathMethod = function(A, B: Integer): Integer of object;
  

это метод, и

 TMathAnonMethod = reference to function(A, B: Integer): Integer;
  

это анонимный метод, но вы также можете назначить обычную функцию или метод переменной этого типа.

Так, например, если

 type
  TMathFn = function(A, B: Integer): Integer;
  TMathMethod = function(A, B: Integer): Integer of object;
  TMathAnonMethod = reference to function(A, B: Integer): Integer;

function Test(A, B: Integer): Integer;
begin
  Result := A   B;
end;

type
  TTestClass = class
    function Test(A, B: Integer): Integer;
  end;

{ TTestClass }

function TTestClass.Test(A, B: Integer): Integer;
begin
  Result := A   B;
end;
  

тогда применяется следующее:

 procedure TForm1.FormCreate(Sender: TObject);
var
  T: TTestClass;
  F: TMathFn;
  M: TMathMethod;
  AM: TMathAnonMethod;
begin

  T := TTestClass.Create;
  try

    F := Test; // compiles
    F := T.Test; // doesn't compile
    F := function(A, B: Integer): Integer
      begin
        Result := A   B;
      end; // doesn't compile

    M := Test; // doesn't compile
    M := T.Test; // compiles
    M := function(A, B: Integer): Integer
      begin
        Result := A   B;
      end; // doesn't compile

    AM := Test; // compiles
    AM := T.Test; // compiles
    AM := function(A, B: Integer): Integer
      begin
        Result := A   B;
      end; // compiles

  finally
    T.Free;
  end;

end;
  

Под капотом, как вы, наверное, уже знаете, F это просто указатель (на функцию) и M указатель на метод. Анонимные методы, с другой стороны, имеют более сложную реализацию на основе интерфейса, которая позволяет использовать всю их магию (например, захват переменных).

Комментарии:

1. ГОРАЗДО лучшее и более полезное объяснение, чем принятый ответ. Интересно, есть ли какой-либо недостаток в обычном использовании «ссылки на» (не то, чтобы я рассматривал возможность этого).

2. @stackmik: Возможно, самым важным недостатком reference to является снижение производительности. У всей его магии есть цена. В подавляющем большинстве приложений это незначительно (потому что компьютеры сегодня действительно быстры), но в критически важных для производительности частях дополнительные затраты на самом деле весьма заметны.

3. И вам также нужны обычные типы процедур, когда вы взаимодействуете с API, такими как Windows API. (Например, используя функции из DLL, процедуры обратного вызова.)