Erik@Grijjy 123 Posted April 27, 2023 We present an implementation of multicast events that is as lightweight as regular Delphi events in common scenario's. Also - for those interested - we show a couple of not-so-common Delphi language features that are used in the implementation. https://blog.grijjy.com/2023/04/27/lightweight-multicast-events/ 2 4 Share this post Link to post
dummzeuch 1506 Posted April 27, 2023 (edited) 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 April 27, 2023 by dummzeuch Share this post Link to post
Anders Melander 1795 Posted April 27, 2023 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. 1 1 Share this post Link to post
Erik@Grijjy 123 Posted April 27, 2023 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
Fr0sT.Brutal 900 Posted April 28, 2023 Nice solution! However, honestly I never had a need for an event with multi-listeners. Share this post Link to post
Anders Melander 1795 Posted April 28, 2023 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
dummzeuch 1506 Posted April 28, 2023 (edited) 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 April 28, 2023 by dummzeuch Share this post Link to post
Anders Melander 1795 Posted April 28, 2023 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. 2 Share this post Link to post
dummzeuch 1506 Posted April 28, 2023 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. 1 Share this post Link to post
Fr0sT.Brutal 900 Posted April 28, 2023 (edited) 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 April 28, 2023 by Fr0sT.Brutal Share this post Link to post
Anders Melander 1795 Posted April 28, 2023 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
Anders Melander 1795 Posted April 28, 2023 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
dummzeuch 1506 Posted April 28, 2023 (edited) 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 April 28, 2023 by dummzeuch 1 Share this post Link to post
Anders Melander 1795 Posted April 28, 2023 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