16

Since anonymous methods appeared in Delphi I wanted to use them in VCL components events. Obviously for backward compatibility the VCL wasn't updated, so I managed to make a simple implementation with a few caveats.

type
  TNotifyEventDispatcher = class(TComponent)
  protected
    FClosure: TProc<TObject>;

    procedure OnNotifyEvent(Sender: TObject);
  public
    class function Create(Owner: TComponent; const Closure: TProc<TObject>): TNotifyEvent; overload;

    function Attach(const Closure: TProc<TObject>): TNotifyEvent;
  end;

implementation

class function TNotifyEventDispatcher.Create(Owner: TComponent; const Closure: TProc<TObject>): TNotifyEvent;
begin
  Result := TNotifyEventDispatcher.Create(Owner).Attach(Closure)
end;

function TNotifyEventDispatcher.Attach(const Closure: TProc<TObject>): TNotifyEvent;
begin
  FClosure := Closure;
  Result := Self.OnNotifyEvent
end;

procedure TNotifyEventDispatcher.OnNotifyEvent(Sender: TObject);
begin
  if Assigned(FClosure) then
    FClosure(Sender)
end;

end.

And this is how it's used for example:

procedure TForm1.FormCreate(Sender: TObject);
begin    
  Button1.OnClick := TNotifyEventDispatcher.Create(Self,
    procedure (Sender: TObject)
    begin
      Self.Caption := 'DONE!'
    end)
end;

Very simple I believe, there are two drawbacks:

  • I have to create a component to manage the lifetime of the anonymous method (I waste a bit more of memory, and it's a bit slower for the indirection, still I prefer more clear code in my applications)

  • I have to implement a new class (very simple) for every event signature. This one is a bit more complicated, still the VCL has very common event signatures, and for every special case when I create the class it's done forever.

What do you think of this implementation? Something to make it better?

chuacw
  • 1,685
  • 1
  • 23
  • 35
pragmatic_programmer
  • 3,566
  • 3
  • 29
  • 36
  • 7
    Reading the question and the answers, all I can see is solutions to a problem that does not exist. I think attempting to use anon methods just trades one set of issues for another set of issues. I can't see any win here. – David Heffernan Nov 06 '11 at 10:27
  • 2
    @DavidHeffernan: It's the coolness of the solution. – dummzeuch Nov 06 '11 at 15:25

3 Answers3

9

You can take a look at my multicast event implementation in DSharp.

Then you can write code like this:

function NotifyEvent(Owner: TComponent; const Delegates: array of TProc<TObject>): TNotifyEvent; overload;
begin
  Result := TEventHandler<TNotifyEvent>.Create<TProc<TObject>>(Owner, Delegates).Invoke;
end;

function NotifyEvent(Owner: TComponent; const Delegate: TProc<TObject>): TNotifyEvent; overload;
begin
  Result := NotifyEvent(Owner, [Delegate]);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.OnClick := NotifyEvent(Button1, [
    procedure(Sender: TObject)
    begin
      Caption := 'Started';
    end,
    procedure(Sender: TObject)
    begin
      if MessageDlg('Continue?', mtConfirmation, mbYesNo, 0) <> mrYes then
      begin
        Caption := 'Canceled';
        Abort;
      end;
    end,
    procedure(Sender: TObject)
    begin
      Caption := 'Finished';
    end]);
end;
chuacw
  • 1,685
  • 1
  • 23
  • 35
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
1

You could make TNotifyEventDispatcher to be a subclass of TInterfacedObject so you do not need to care about freeing it.

But to be more pragmatic one would use traditional event assignment that takes less lines of code and is supported by the IDE.

andrius
  • 693
  • 4
  • 8
  • I don't think event assignment is the problem. And using traditional event doesn't take that many less lines if you consider the event handler methods you need as well. I think what the OP is after is not having to code a whole bunch of separate event handler methods in each form. Which I can sympathize with. Unfortunately the event framework in Delphi doesn't (yet) allow for `reference to proc`'s. – Marjan Venema Nov 06 '11 at 08:43
  • 1
    TInterfacedObject can only help if there is something to keep the reference alive. There is nothing obvious. TComponent.Owner is the way to go. – David Heffernan Nov 06 '11 at 18:06
1

Interesting approach.

(Disclaimer: haven't checked this, but it is something to investigate): You may have to be careful though about what goes on in capturing the state of the method that "assigns" the anonymous method to the event. Capturing can be an advantage but can also have side effects you do not want. If your anonymous method needs info about the form at the time it is fired, it may end up with info at the time of its assignment. Update: apparently this is not the case, see comment by Stefan Glienke.

You do not really need different classes. Using overloading you could create different class Create functions that each take a specific signature and return the corresponding event handler and the compiler will sort it out.

Managing lifetime could be simplified if you derived from TInterfacedObject instead of TComponent. Reference counting should then take care of destroying the instance when it is no longer used by the form. Update: this does require keeping a reference to the instance somewhere in the form, or refcounting won't help as the instance will be freed immediately after assigning the notify event. You could add an extra parameter on the class Create functions to which you pass a method that the instance can useto add itself to some list of the form.

Side note: All in all though I have to agree with David in his comment on the question: it does sound like a lot of work for the "sole purpose" of using anonymous methods...

Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • Capturing of variables is done by address. There is nothing like getting some old value. In the case of the OP Self gets captured which is a pointer anyway. Even if he would capture some string or integer variable and assign another value after the anonymous method assignment he will get that value when the method is called. Also TInterfacedObject will do nothing here because there is nothing to keep the interface reference. Lifetime management is done via the owner. – Stefan Glienke Nov 06 '11 at 10:09
  • @StefanGlienke: thanks for the info on the capturing. And yes, you are right, as long as you do not assign the result of the constructor anywhere, interface refcounting is not going to help. – Marjan Venema Nov 06 '11 at 14:22