Delphi 使用匿名方法的VCL事件-您如何看待此实现?

Delphi 使用匿名方法的VCL事件-您如何看待此实现?,delphi,vcl,anonymous-methods,Delphi,Vcl,Anonymous Methods,由于匿名方法出现在Delphi中,我想在VCL组件事件中使用它们。显然,为了向后兼容,VCL没有更新,所以我设法做了一个简单的实现,并提出了一些警告 type TNotifyEventDispatcher = class(TComponent) protected FClosure: TProc<TObject>; procedure OnNotifyEvent(Sender: TObject); public class function Cr

由于匿名方法出现在Delphi中,我想在VCL组件事件中使用它们。显然,为了向后兼容,VCL没有更新,所以我设法做了一个简单的实现,并提出了一些警告

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.
很简单,我认为有两个缺点:

  • 我必须创建一个组件来管理匿名方法的生存期(我浪费了更多的内存,间接寻址的速度也慢了一点,但我更喜欢应用程序中更清晰的代码)

  • 我必须为每个事件签名实现一个新类(非常简单)。这一个有点复杂,但是VCL有非常常见的事件签名,对于我创建类时的每一个特殊情况,它永远都会完成


您认为这个实现如何?有什么可以让它更好吗?

您可以将
TNotifyEventDispatcher
设置为
TInterfacedObject
的子类,这样您就不必担心释放它了

但为了更加实用,我们将使用传统的事件分配,它占用更少的代码行,并且由IDE支持。

有趣的方法

(免责声明:尚未对此进行检查,但需要进行调查):在捕获将匿名方法“分配”给事件的方法的状态时,您可能需要小心。捕获可能是一种优势,但也可能产生您不想要的副作用。如果您的匿名方法在激发窗体时需要有关窗体的信息,则它可能在分配窗体时得到信息更新:显然情况并非如此,请参见Stefan Glienke的评论

你真的不需要不同的课程。使用重载,您可以创建不同的
类create
函数,每个函数都具有特定的签名并返回相应的事件处理程序,编译器将对其进行排序

如果从TInterfacedObject而不是TComponent派生,则可以简化生命周期管理。然后,引用计数应注意在窗体不再使用实例时销毁该实例更新:这确实需要在表单中的某个位置保留对实例的引用,否则refcounting将没有帮助,因为实例将在分配notify事件后立即被释放。您可以在类Create函数上添加一个额外的参数,向该类Create函数传递一个方法,实例可以使用该方法将自身添加到表单的某个列表中

旁注:总而言之,尽管我不得不同意David对这个问题的评论:这听起来像是为了使用匿名方法的“唯一目的”做了很多工作……

你可以看看我的答案

然后您可以编写如下代码:

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;
函数NotifyEvent(所有者:TComponent;常量委托:TProc数组):TNotifyEvent;超载;
开始
结果:=TEventHandler.Create(所有者、委托).Invoke;
结束;
函数NotifyEvent(所有者:TComponent;常量委托:TProc):TNotifyEvent;超载;
开始
结果:=NotifyEvent(所有者,[委托]);
结束;
过程TForm1.FormCreate(发送方:TObject);
开始
Button1.OnClick:=通知事件(Button1[
程序(发送方:TObject)
开始
标题:=“已开始”;
完,,
程序(发送方:TObject)
开始
如果MessageDlg('Continue?',mtConfirmation,mbYesNo,0)mrYes则
开始
标题:='取消';
中止
结束;
完,,
程序(发送方:TObject)
开始
标题:=“完成”;
(完),;
结束;

我认为事件分配不是问题所在。如果您还需要考虑事件处理程序的方法,使用传统事件不会占用那么多的行。我认为OP所追求的是不必在每个表单中编写一大堆单独的事件处理程序方法。我对此表示同情。不幸的是,Delphi中的事件框架(还)不允许对proc的
引用。TInterfacedObject只能在有什么东西可以保持引用活动的情况下提供帮助。没有什么明显的。TComponent.Owner是一种方法。变量的捕获是通过地址完成的。没有什么比得到一些旧的价值更重要的了。在OP Self被捕获的情况下,它是一个指针。即使他会捕获一些字符串或整数变量并在匿名方法赋值后赋值,他也会在调用该方法时得到该值。此外,TInterfacedObject在这里不会做任何事情,因为没有任何东西可以保留接口引用。生命周期管理是通过所有者完成的。@StefanGlienke:感谢您提供有关捕获的信息。是的,你是对的,只要你不把构造函数的结果分配到任何地方,接口refcounting就不会有帮助。阅读问题和答案,我所能看到的只是一个不存在的问题的解决方案。我认为尝试使用anon方法只会将一组问题换成另一组问题。我在这里看不到任何胜利。@DavidHeffernan:这是解决方案的冷静。嗨,DSharp.Core.Events.pas在哪里?我在DSharp存储库中看不到此单元。
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;