Can someone explain Anonymous methods to me?

2019-03-09 18:00发布

问题:

Delphi 2009, among some cool stuff, has also just got Anonymous methods. I've seen the examples, and the blog posts regarding anonymous methods, but I don't get them yet. Can someone explain why I should be excited?

回答1:

Just think of typical callback code where you need to have data available to the callback. Often this data is needed for the callback only, yet you have to jump through a number of hoops to get it there without having to resign to un-OOP-friendly practices like global variables. With anonymous methods the data can stay where it is - you don't have to unnecessarily extend its scope or copy it to some helper object. Just write your callback code in-place as an anonymous method and it can fully access and manipulate all local variables at the site where the anonymous method is defined (not where it's called!).

There are other aspects of anonymous methods, most obviously the fact that they are, well: anonymous, but this is the one that really made them go "click" for me...



回答2:

Please have a look at closures.

Delphi anonymous functions are closures.

These are created within other functions and as such has access to the scope of that function. This is even so if the anonumous function is assigned to a function parameter that is called after the original function is called. (I will create an example in a moment).

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;

The above method assigns two anonymous functions to the fields F1 and F2. The first increases the local variable and the second showe the value of the variable.

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

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

You can now call both functions, and they access the same a. So calling F1 twice and F2 once shows a 3. Of course this is a simple example. But it can be expanded to more usefull code.

In the multi threading environment, anonymous functions can be used in a call to Synchronise, which eliminates the need for countless methods.



回答3:

May be this example can be of some value for you. Here I'm going to implement a zoomable display list for drawing on a TCanvas without declaring different types of display classes. It also makes heavy use of Generics. Assume we have a TForm with a TPaintBox and a TTrackBar on it...

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;


回答4:

People have already provided the code, so I'll just list some places where they can be useful.

Say you have some GUI code. Normally, for something like a button's onclick handler, you have to provide a function that will be called when that button is clicked. However, let's say all that function has to do is something simple like pop up a message box or set a field somewhere. Let's say you have dozens of these buttons throughout your code. Without anonymous functions, you'll have to have tons of functions called "OnButton1Click," "OnExitButtonClick," etc, which will likely clutter up your code... or you can create anonymous functions that immediately attach to these events, and you don't have to worry about them anymore.

Another use is functional programming. Say you have a list of numbers. You want to get back only those numbers that are divisible by three. There is likely a function called filter which takes a function that returns a boolean and a list, and returns a new list containing only those elements in the first list that, when passed to the function, returned True. Example:

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

It'd be annoying to be forced to define a function "isDivisibleByThree", then pass it to filter, so another use for anonymous functions here would be to just quickly create a function you won't need anywhere else and pass it to filter.



回答5:

I'm answering my own question, but I found a good explanation of anonymous methods here Can your programming language do this?



回答6:

I guess (I don't know Delphi) this implies that you can create functions as a kind of data object now. This means that you can, for example, pass functions as parameters to other functions. Example: A sort function might take a comparison function as a parameter, thus being much more versatile.



回答7:

Anonymous methods are useful in functional programming, but they also can help you to write a more compact code in structured programming. Threading, for example: http://blogs.codegear.com/abauer/2008/09/08/38868

Another use cases for your 'excitement' :) : http://delphi.fosdal.com/2008/08/anonymous-methods-when-to-use-them.html