Может ли кто-нибудь объяснить мне анонимные методы?

Delphi 2009, среди некоторых интересных вещей, также только что получил анонимные методы. Я видел примеры и сообщения в блогах об анонимных методах, но я их еще не понял. Может кто-нибудь объяснить, почему я должен быть взволнован?


person Steve    schedule 01.11.2008    source источник


Ответы (7)


Просто подумайте о типичном коде обратного вызова, где вам нужно иметь данные, доступные для обратного вызова. Часто эти данные нужны для обратного вызова только, но вам нужно пройти через ряд обручей, чтобы получить их, не прибегая к недружественным к ООП методам, таким как глобальные переменные. С помощью анонимных методов данные могут оставаться там, где они есть — вам не нужно без необходимости расширять их область действия или копировать их в какой-либо вспомогательный объект. Просто напишите свой код обратного вызова как анонимный метод, и он сможет получить полный доступ ко всем локальным переменным и манипулировать ими на сайте, где определен анонимный метод (а не там, где он вызывается!).

Есть и другие аспекты анонимных методов, наиболее очевидным является тот факт, что они, ну, анонимные, но это то, что действительно заставило их "щелкнуть" для меня...

person Oliver Giesen    schedule 05.11.2008
comment
Это единственный комментарий, который заставил меня сказать «АГА». Пока не на 100% взволнован, но, по крайней мере, теперь я вижу его реальное применение. - person Steve; 06.11.2008

Пожалуйста, взгляните на замыкания.

Анонимные функции Delphi являются замыканиями.

Они создаются внутри других функций и поэтому имеют доступ к области действия этой функции. Это даже так, если анонимная функция назначается параметру функции, который вызывается после вызова исходной функции. (Я создам пример через мгновение).

type
  TAnonFunc = reference to procedure;
  TForm2 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    F1 : TAnonFunc;
    F2 : TAnonFunc;
  end;

procedure TForm2.Button1Click(Sender: TObject);
var
  a : Integer;
begin
  a := 1;

  F1 := procedure
  begin
    a := a + 1;
  end;

  F2 := procedure
  begin
    Memo1.Lines.Add(IntToStr(a));
  end;
end;

В приведенном выше методе полям F1 и F2 присваиваются две анонимные функции. Первый увеличивает локальную переменную, а второй показывает значение переменной.

procedure TForm2.Button2Click(Sender: TObject);
begin
  F1;
end;

procedure TForm2.Button3Click(Sender: TObject);
begin
  F2;
end;

Теперь вы можете вызывать обе функции, и они получают доступ к одному и тому же a. Таким образом, вызов F1 дважды и F2 один раз показывает 3. Конечно, это простой пример. Но его можно расширить до более полезного кода.

В многопоточной среде анонимные функции могут использоваться в вызове Synchronise, что устраняет необходимость в бесчисленных методах.

person Toon Krijthe    schedule 01.11.2008
comment
хорошо, но я все еще не взволнован! Я в восторге от дженериков, потому что вижу сотни мест, где их можно использовать. Анонимные методы до сих пор оставляют меня равнодушным. - person Steve; 02.11.2008
comment
Не надо волноваться, вы найдете им применение ;-). - person Toon Krijthe; 02.11.2008
comment
Возможно, взволнован - не то слово, но я имею в виду, что я не могу придумать пример из реальной жизни (пока), где бы я сказал: "Да, это как раз то, для чего предназначены методы Anaoymous". - person Steve; 02.11.2008
comment
Примером может служить универсальный класс с методом функции, который должен быть создан при создании класса. - person Toon Krijthe; 05.11.2008
comment
Этот пример ошеломил меня, разве уже не исчезло из стека, когда вызывается Button2Click (или Button3Click)? Это просто локальная переменная! Или это не так, если это используется в аноне. функция??? О, надо стиснуть зубы и попробовать D2009, может быть :) - person utku_karatas; 16.12.2008
comment
Да, это локальная переменная, но создается скрытый объект, содержащий состояние. - person Toon Krijthe; 31.08.2009

Может быть, этот пример может иметь для вас какое-то значение. Здесь я собираюсь реализовать масштабируемый список отображения для рисования на TCanvas без объявления различных типов классов отображения. Он также активно использует дженерики. Предположим, у нас есть TForm с TPaintBox и TTrackBar...

type
  TDisplayProc = TProc<TCanvas>;

type
  TFrmExample3 = class(TForm)
    pbxMain: TPaintBox;
    trkZoom: TTrackBar;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure pbxMainClick(Sender: TObject);
    procedure pbxMainMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure pbxMainPaint(Sender: TObject);
    procedure trkZoomChange(Sender: TObject);
  private
    FDisplayList: TList<TDisplayProc>;
    FMouseX: Integer;
    FMouseY: Integer;
    FZoom: Extended;
    procedure SetZoom(const Value: Extended);
  protected
    procedure CreateCircle(X, Y: Integer);
    procedure CreateRectangle(X, Y: Integer);
    function MakeRect(X, Y, R: Integer): TRect;
  public
    property Zoom: Extended read FZoom write SetZoom;
  end;

implementation

{$R *.dfm}

procedure TFrmExample3.PaintBox1Paint(Sender: TObject);
var
  displayProc: TDisplayProc;
begin
  for displayProc in FDisplayList do
    displayProc((Sender as TPaintBox).Canvas);
end;

procedure TFrmExample3.CreateCircle(X, Y: Integer);
begin
  FDisplayList.Add(
    procedure (Canvas: TCanvas)
    begin
      Canvas.Brush.Color := clYellow;
      Canvas.Ellipse(MakeRect(X, Y, 20));
    end
  );
end;

procedure TFrmExample3.CreateRectangle(X, Y: Integer);
begin
  FDisplayList.Add(
    procedure (Canvas: TCanvas)
    begin
      Canvas.Brush.Color := clBlue;
      Canvas.FillRect(MakeRect(X, Y, 20));
    end
  );
end;

procedure TFrmExample3.FormCreate(Sender: TObject);
begin
  FDisplayList := TList<TDisplayProc>.Create;
end;

procedure TFrmExample3.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FDisplayList);
end;

function TFrmExample3.MakeRect(X, Y, R: Integer): TRect;
begin
  Result := Rect(Round(Zoom*(X - R)), Round(Zoom*(Y - R)), Round(Zoom*(X + R)), Round(Zoom*(Y + R)));
end;

procedure TFrmExample3.pbxMainClick(Sender: TObject);
begin
  case Random(2) of
    0: CreateRectangle(Round(FMouseX/Zoom), Round(FMouseY/Zoom));
    1: CreateCircle(Round(FMouseX/Zoom), Round(FMouseY/Zoom));
  end;
  pbxMain.Invalidate;
end;

procedure TFrmExample3.pbxMainMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  FMouseX := X;
  FMouseY := Y;
end;

procedure TFrmExample4.SetZoom(const Value: Extended);
begin
  FZoom := Value;
  trkZoom.Position := Round(2*(FZoom - 1));
end;

procedure TFrmExample4.trkZoomChange(Sender: TObject);
begin
  Zoom := 0.5*(Sender as TTrackBar).Position + 1;
  pbxMain.Invalidate;
end;
person Uwe Raabe    schedule 02.11.2008

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

Скажем, у вас есть код графического интерфейса. Обычно для чего-то вроде обработчика нажатия кнопки вы должны предоставить функцию, которая будет вызываться при нажатии этой кнопки. Однако предположим, что все, что эта функция должна сделать, это что-то простое, например, открыть окно сообщения или установить где-нибудь поле. Допустим, в вашем коде есть десятки таких кнопок. Без анонимных функций вам придется иметь множество функций с именами «OnButton1Click», «OnExitButtonClick» и т. д., которые, скорее всего, загромождают ваш код… или вы можете создавать анонимные функции, которые сразу же присоединяются к этим событиям, и вам можно больше не беспокоиться о них.

Другое применение — функциональное программирование. Скажем, у вас есть список чисел. Вы хотите получить обратно только те числа, которые делятся на три. Вероятно, существует функция с именем filter, которая принимает функцию, возвращающую логическое значение и список, и возвращает новый список, содержащий только те элементы в первом списке, которые при передаче в функцию вернули True. Пример:

filter(isOdd, [1, 2, 3, 5, 6, 9, 10]) --> [1, 3, 5, 9]

Было бы неприятно, если бы вас заставляли определять функцию «isDivisibleByThree», а затем передавать ее в фильтр, поэтому другим использованием анонимных функций здесь было бы просто быстро создать функцию, которая вам больше нигде не понадобится, и передать ее в фильтр.

person Claudiu    schedule 02.11.2008

Я отвечаю на свой вопрос, но я нашел хорошее объяснение анонимных методов здесь Can ваш язык программирования делает это?

person Steve    schedule 02.11.2008

Я думаю (я не знаю Delphi), это означает, что теперь вы можете создавать функции как своего рода объект данных. Это означает, что вы можете, например, передавать функции в качестве параметров другим функциям. Пример: функция сортировки может принимать в качестве параметра функцию сравнения, что делает ее более универсальной.

person Svante    schedule 01.11.2008
comment
Частично. В Delphi уже есть указатели на функции. Но теперь их можно создавать анонимно. Смотрите мой комментарий. - person Toon Krijthe; 02.11.2008

Анонимные методы полезны в функциональном программировании, но они также могут помочь вам написать более компактный код в структурном программировании. Например, многопоточность: http://blogs.codegear.com/abauer/2008/09/08/38868

Еще один пример использования вашего «волнения» :) : http://delphi.fosdal.com/2008/08/anonymous-methods-when-to-use-them.html

person F.D.Castel    schedule 02.11.2008