Delphi 2009, среди некоторых интересных вещей, также только что получил анонимные методы. Я видел примеры и сообщения в блогах об анонимных методах, но я их еще не понял. Может кто-нибудь объяснить, почему я должен быть взволнован?
Может ли кто-нибудь объяснить мне анонимные методы?
Ответы (7)
Просто подумайте о типичном коде обратного вызова, где вам нужно иметь данные, доступные для обратного вызова. Часто эти данные нужны для обратного вызова только, но вам нужно пройти через ряд обручей, чтобы получить их, не прибегая к недружественным к ООП методам, таким как глобальные переменные. С помощью анонимных методов данные могут оставаться там, где они есть — вам не нужно без необходимости расширять их область действия или копировать их в какой-либо вспомогательный объект. Просто напишите свой код обратного вызова как анонимный метод, и он сможет получить полный доступ ко всем локальным переменным и манипулировать ими на сайте, где определен анонимный метод (а не там, где он вызывается!).
Есть и другие аспекты анонимных методов, наиболее очевидным является тот факт, что они, ну, анонимные, но это то, что действительно заставило их "щелкнуть" для меня...
Пожалуйста, взгляните на замыкания.
Анонимные функции 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, что устраняет необходимость в бесчисленных методах.
Может быть, этот пример может иметь для вас какое-то значение. Здесь я собираюсь реализовать масштабируемый список отображения для рисования на 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;
Люди уже предоставили код, поэтому я просто перечислю несколько мест, где они могут быть полезны.
Скажем, у вас есть код графического интерфейса. Обычно для чего-то вроде обработчика нажатия кнопки вы должны предоставить функцию, которая будет вызываться при нажатии этой кнопки. Однако предположим, что все, что эта функция должна сделать, это что-то простое, например, открыть окно сообщения или установить где-нибудь поле. Допустим, в вашем коде есть десятки таких кнопок. Без анонимных функций вам придется иметь множество функций с именами «OnButton1Click», «OnExitButtonClick» и т. д., которые, скорее всего, загромождают ваш код… или вы можете создавать анонимные функции, которые сразу же присоединяются к этим событиям, и вам можно больше не беспокоиться о них.
Другое применение — функциональное программирование. Скажем, у вас есть список чисел. Вы хотите получить обратно только те числа, которые делятся на три. Вероятно, существует функция с именем filter
, которая принимает функцию, возвращающую логическое значение и список, и возвращает новый список, содержащий только те элементы в первом списке, которые при передаче в функцию вернули True. Пример:
filter(isOdd, [1, 2, 3, 5, 6, 9, 10]) --> [1, 3, 5, 9]
Было бы неприятно, если бы вас заставляли определять функцию «isDivisibleByThree», а затем передавать ее в фильтр, поэтому другим использованием анонимных функций здесь было бы просто быстро создать функцию, которая вам больше нигде не понадобится, и передать ее в фильтр.
Я отвечаю на свой вопрос, но я нашел хорошее объяснение анонимных методов здесь Can ваш язык программирования делает это?
Я думаю (я не знаю Delphi), это означает, что теперь вы можете создавать функции как своего рода объект данных. Это означает, что вы можете, например, передавать функции в качестве параметров другим функциям. Пример: функция сортировки может принимать в качестве параметра функцию сравнения, что делает ее более универсальной.
Анонимные методы полезны в функциональном программировании, но они также могут помочь вам написать более компактный код в структурном программировании. Например, многопоточность: http://blogs.codegear.com/abauer/2008/09/08/38868
Еще один пример использования вашего «волнения» :) : http://delphi.fosdal.com/2008/08/anonymous-methods-when-to-use-them.html