有人可以向我解释匿名方法吗?

时间:2008-11-01 21:37:01

标签: delphi closures anonymous-methods

Delphi 2009,在一些很酷的东西中,也有匿名方法。我已经看过这些示例,以及关于匿名方法的博客文章,但我还没有得到它们。有人可以解释为什么我应该感到兴奋吗?

7 个答案:

答案 0 :(得分:16)

请查看closures

Delphi匿名函数是闭包。

这些是在其他功能中创建的,因此可以访问该功能的范围。如果将anonumous函数分配给在调用原始函数后调用的函数参数,则情况更是如此。 (我稍后会创建一个例子。)

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。 当然这是一个简单的例子。但它可以扩展到更有用的代码。

在多线程环境中,可以在对Synchronize的调用中使用匿名函数,这样就无需使用无数的方法。

答案 1 :(得分:12)

只需考虑典型的回调代码,您需要将数据提供给回调。回调通常需要这些数据,但是你必须跳过一些箍来实现它,而不必辞去全局变量等非OOP友好的做法。使用匿名方法,数据可以保持原样 - 您不必不必要地扩展其范围或将其复制到某个辅助对象。只需将您的回调代码作为匿名方法就地编写,它可以完全访问和操作定义匿名方法的站点上的所有局部变量(而不是它被调用的位置!)。

匿名方法还有其他方面,最明显的是它们是,好吧:匿名,但这是真正使他们为我“点击”的那个......

答案 2 :(得分:11)

可能这个例子可以为你带来一些价值。在这里,我将实现一个可缩放的显示列表,用于在TCanvas上绘制而不声明不同类型的显示类。它也大量使用泛型。假设我们有一个带有TPaintBox和TTrackBar的TForm ......

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;

答案 3 :(得分:5)

人们已经提供了代码,所以我只列出一些有用的地方。

假设你有一些GUI代码。通常,对于像按钮的onclick处理程序这样的东西,你必须提供一个在单击该按钮时调用的函数。但是,假设所有功能必须做的事情就像弹出消息框或在某处设置字段一样简单。假设您的代码中有几十个按钮。如果没有匿名函数,你将不得不拥有大量名为“OnButton1Click”,“OnExitButtonClick”等的函数,它们可能会使你的代码混乱......或者你可以创建立即附加到这些事件的匿名函数,并且你不要不用再担心了。

另一个用途是函数式编程。假设您有一个数字列表。你想只找回那些可以被三整除的数字。可能有一个名为filter的函数,它接受一个返回布尔值和列表的函数,并返回一个新列表,该列表仅包含第一个列表中的那些元素,当传递给函数时,返回True。例如:

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

被迫定义一个函数“isDivisibleByThree”然后将它传递给过滤器会很烦人,所以这里匿名函数的另一个用途就是快速创建一个你不需要的函数并传递给它过滤。

答案 4 :(得分:5)

我正在回答我自己的问题,但我在这里找到了对匿名方法的一个很好的解释 Can your programming language do this?

答案 5 :(得分:1)

我猜(我不知道Delphi)这意味着您现在可以将函数创建为一种数据对象。这意味着您可以将函数作为参数传递给其他函数。示例:排序函数可能将比较函数作为参数,因此更加通用。

答案 6 :(得分:1)

匿名方法在函数式编程中很有用,但它们也可以帮助您在结构化编程中编写更紧凑的代码。线程,例如:http://blogs.codegear.com/abauer/2008/09/08/38868

“兴奋”的另一个用例:):http://delphi.fosdal.com/2008/08/anonymous-methods-when-to-use-them.html