【问题标题】:VCL events with anonymous methods - what do you think about this implementation?带有匿名方法的 VCL 事件 - 你如何看待这个实现?
【发布时间】:2011-12-22 22:52:41
【问题描述】:

由于匿名方法出现在 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.

这就是它的用法,例如:

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

我相信很简单,有两个缺点:

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

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

你觉得这个实现怎么样?有什么可以让它变得更好?

【问题讨论】:

  • 阅读问题和答案,我能看到的只是一个不存在的问题的解决方案。我认为尝试使用匿名方法只是将一组问题换成另一组问题。我在这里看不到任何胜利。
  • @DavidHeffernan:这就是解决方案的酷炫之处。

标签: delphi vcl anonymous-methods


【解决方案1】:

您可以将TNotifyEventDispatcher 设为TInterfacedObject 的子类,这样您就无需关心释放它。

但更实用的方法是使用传统的事件分配,它需要更少的代码行并且受 IDE 支持。

【讨论】:

  • 我认为事件分配不是问题。如果您还考虑需要的事件处理程序方法,使用传统事件并不会减少那么多行。我认为 OP 所追求的是不必在每种形式中编写一大堆单独的事件处理程序方法。我可以同情。不幸的是,Delphi 中的事件框架(还)不允许reference to proc's。
  • TInterfacedObject 只有在有什么东西可以让引用保持活动时才能提供帮助。没有什么明显的。 TComponent.Owner 是要走的路。
【解决方案2】:

有趣的方法。

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

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

如果您从 TInterfacedObject 而不是 TComponent 派生,则可以简化管理生命周期。当表单不再使用实例时,引用计数应该负责销毁实例。 更新:这确实需要在表单中的某处保留对实例的引用,否则引用计数将无济于事,因为实例将在分配通知事件后立即被释放。您可以在类创建函数上添加一个额外参数,您可以向该函数传递一个方法,实例可以使用该方法将自身添加到表单的某个列表中。

旁注: 总而言之,尽管我必须同意大卫对这个问题的评论:对于使用匿名方法的“唯一目的”来说,这听起来确实有很多工作...... .

【讨论】:

  • 变量的捕获是通过地址完成的。没有什么比获得一些旧的价值更重要的了。在 OP Self 被捕获的情况下,无论如何它都是一个指针。即使他会捕获一些字符串或整数变量并在匿名方法分配之后分配另一个值,他也会在调用该方法时获得该值。 TInterfacedObject 在这里也不会做任何事情,因为没有什么可以保留接口引用。终身管理由所有者完成。
  • @StefanGlienke:感谢您提供有关捕获的信息。是的,你是对的,只要你不在任何地方分配构造函数的结果,接口引用计数就无济于事。
【解决方案3】:

你可以看看我的multicast event implementation in 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;

【讨论】:

  • 嗨,DSharp.Core.Events.pas 在哪里?我在 DSharp 存储库中看不到这个单元。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多