Jump to content
Erik@Grijjy

Lightweight Multicast Events

Recommended Posts

Unfortunately this will only work with Delphi 10.4 and higher, because it uses Custom Managed Records. But it's still a very interesting implementation which could be ported back to older Delphi versions that supports generics with some hacks, or even older (down to Delphi 2007 or maybe even 2006) by replacing the generics with custom event declarations. But of course that would make it a lot less convenient.

Edited by dummzeuch

Share this post


Link to post
7 minutes ago, dummzeuch said:

Unfortunately this will only work with Delphi 10.4 and higher, because it uses Custom Managed Records.

For older versions there's always this one: Multicast Delegates - An amazingly simple and elegant solution and, as far as I can tell, even more versatile.

 

  • Like 1
  • Thanks 1

Share this post


Link to post
51 minutes ago, dummzeuch said:

Unfortunately this will only work with Delphi 10.4 and higher, because it uses Custom Managed Records. 

As Anders mentioned above, there are other solutions out there that work with older Delphi versions.

My focus was on keeping it extremely light weight so it doesn't have more overhead than regular events in common cases. And Custom Managed Records are one of the things that make that possible. But you could remove the CMR functionality, but then the users are responsible for cleaning up the multicast events. But that may be only a small price to pay.

Share this post


Link to post
4 minutes ago, Fr0sT.Brutal said:

However, honestly I never had a need for an event with multi-listeners.

Are you sure? It's the observer pattern.

 

I use it literally all the time, like daily, but I must admit that even though it would be much easier to implement as delegates, so far I have always implemented it via interfaces:

type
  IMyNotification = interface
    [...GUID...]
    procedure MyNotification(...params...);
  end;

  TSomeClass = class
  private
    FSubscriptions: TList<IMyNotification>;
  protected
    procedure Notify(...params...);
  public
    destructor Destroy; override;
    procedure Subscribe(const Subscriber: IMyNotification);
    procedure Unsubscribe(const Subscriber: IMyNotification);
  end;
      
destructor TSomeClass.Destroy;
begin
  FSubscriptions.Free;
  inherited;
end;

procedure TSomeClass.Notify(...params...);
begin
  if (FSubscriptions <> nil) then
    for var Subscriber in FSubscriptions do
      Subscriber.MyNotification(...params...);
end;
      
procedure TSomeClass.Subscribe(const Subscriber: IMyNotification);
begin
  if (FSubscriptions = nil) then
    FSubscriptions := TList<IMyNotification>.Create;
  FSubscriptions.Add(Subscriber);
end;

procedure TSomeClass.Unsubscribe(const Subscriber: IMyNotification);
begin
  if (FSubscriptions <> nil) then
    FSubscriptions.Remove(Subscriber);
end;

versus

type
  TMyNotification = procedure(...params...);

  TSomeClass = class
  private
    FMyEvent: TDelegate<TMyNotification>;
  protected
    procedure Notify(...params...);
  public
    property MyEvent: IDelegate<TMyNotification> read GetMyEvent;
  end;
      
function TSomeClass.GetMyEvent: IDelegate<TMyNotification>;
begin
  Result := FMyEvent;
end;

procedure TSomeClass.Notify(...params...);
begin
  for var Delegate in FMyEvent do
    Delegate(...params...);
end;

 

Share this post


Link to post
34 minutes ago, Fr0sT.Brutal said:

Nice solution! However, honestly I never had a need for an event with multi-listeners.

Out of the top of my head I have at least one idea: Our internal programs all use a configuration frame class that, when created, hooks the form's OnClose event so it can save the configuration when the form is closed. If that event is already assigned, it needs to call the original method. So a multicast event would have saved some code. But unfortunately this implementation won't help because it doesn't magically make OnClose a multicast event.

Edited by dummzeuch

Share this post


Link to post
27 minutes ago, dummzeuch said:

But unfortunately this implementation won't help because it doesn't magically make OnClose a multicast event.

The solution seems obvious... Don't use the OnClose event.

Have the form broadcast a notification that it is closing and have the frames subscribe to this notification. eAsY.

  • Like 2

Share this post


Link to post
8 minutes ago, Anders Melander said:

The solution seems obvious... Don't use the OnClose event.

Have the form broadcast a notification that it is closing and have the frames subscribe to this notification. eAsY.

Even easier: Just continue using the current, working solution. I was just giving an example for a possible use case.

  • Haha 1

Share this post


Link to post
47 minutes ago, dummzeuch said:

Our internal programs all use a configuration frame class that, when created, hooks the form's OnClose event so it can save the configuration when the form is closed

I use custom form descendant for this:

TAppForm = class(TForm)

... // config stuff, helper methods, etc

  // Create, Destroy, ReadState, ... overrides

end;

 

TMainForm = class(TAppForm)

 

Thus you have all events non-occupied

48 minutes ago, Anders Melander said:

I use it literally all the time, like daily

Applied to which classes? This pattern is basic for pub/sub and so on but how about GUI components, like in the article? I'd like to know any real world applications

Edited by Fr0sT.Brutal

Share this post


Link to post
20 minutes ago, Fr0sT.Brutal said:

Applied to which classes?

Any classes that need to know what's going on with other classes, state changes, etc.

 

I basically only implement classic events if I have a component that needs design-time event hookup via the object inspector. For everything else, it's notifications via interfaces (i.e. multicast). It has a higher cost up-front in terms of writing the code but down the line, it saves me when inevitably I need more than one notification receiver.

 

26 minutes ago, Fr0sT.Brutal said:

I'd like to know any real world applications

@dummzeuch gave a real-world example. Yes, it can be solved in multiple ways. He solved it with a delegate chain (very fragile). You solved it by having the event source not use the event slot, but that still leaves you with a single-cast event. What if you have a form with multiple frames that all need to know when the state changes?

With multi-cast you don't need to worry about if you will ever need to have more than one event consumer. With interfaces there's the added benefit of separation and, if you design things for it, no dependencies between the different parts.

Share this post


Link to post
48 minutes ago, dummzeuch said:

Even easier: ...

Yeah, but nobody ever asked me to implement the easy solution. For some reason, they all want the hard stuff.

Share this post


Link to post
1 hour ago, Anders Melander said:

Yeah, but nobody ever asked me to implement the easy solution. For some reason, they all want the hard stuff.

Odd. All they ever ask me to implement is a working solution in as short a time as possible (finished: Yesterday)

When they get it, they sometimes are actually content with that, but most of the time they then start complaining about missing features, stability, performance, UI ...

Edited by dummzeuch
  • Haha 1

Share this post


Link to post
32 minutes ago, dummzeuch said:

Odd. All they ever ask me to implement is a working solution in as short a time as possible (finished: Yesterday)

When they get it, they sometimes are actually content with that, but most of the time they then start complaining about missing features, stability, performance, UI ...

Fair enough, but I'm guessing that if you made the consequences of their choice clear to them they might reconsider. Remember to offer more than two choices: The bad choice, the right choice, and the super deluxe, over-the-top, choice. That way they can feel that they have a say in the matter 🙂

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×