It is fairly straight forward. I found actions, events etc so confounding i never really could get them to work in the most generic sense.
I pretty much gave up when trying to make a generic text modifier.
So i made to interfaces, one, IChangetext, has one function ChangeText(string newText).
The other, INotifyTexts, has three functions.
AddSub(IChangeText iChangeText), RemoveSub(IChangeText) and NotifyTexts(string newText).
The class that implements INotifyTexts has a hashset from which subscribers are removed and added.
Unless I am missing something, it works fine. Maybe passing delegate would be problematic, but overall, this seems a much simpler set up than action, events, UnityEvents, EventArgs etcā¦
I use events etc for more coupled actions, but this two interface system and a hashset seems to be so much simpler for me to understand.
I doubt I have done anything but reinvent a wheel, pr maybe this is just good use of interfaces, and not really an event system, but I can already see how many different ways I could use this pattern for quite generic actions.
You have implemented a variant of an observer pattern. This is a close cousin to an event handling system, and in fact, some would argue that itās the same thing. However, there is a lot of plumbing that goes under the hood, and therefore, at least implementation-wise, each and every event handling system is different in some regard.
Hereās for example Observer design pattern on the MS site, check out the code box below and youāll find something similar to what you did.
You only need to make sure that you actually service any of the finer points of an observer pattern:
In fact, when implemented naively, you get something that is typically much less robust and/or resilient to stress loads of real production code.
You can find more about the details under āStrong vs. weak referenceā ā¦
⦠and under āCoupling and typical publish-subscribe implementationsā where you can learn more about the publish-subscribe pattern that is said to be a sibling of a messaging queue paradigm, and so on. There is a lot to be said about coupling as well, and normally event systems are designed so that event raisers donāt know anything about the event consumers which helps a lot against the inevitable two-way dependency hell.
That said, UnityEvents have an added benefit of working in the inspector and can be hooked to component methods via GUI. Thatās actually one of the main reasons they exist.
On the other hand you have a vanilla C# event system that relies on multicast delegates, and no, you donāt have to use EventHandler and EventArgs generic templates ā even though they say āScenarios that require you to define a delegate are rareā ā I actually hate this convention because itās honestly more boilerplate-y and less coding friendly than just using custom delegates, in my opinion. I also donāt like the way they present the handler delegates as something that should be reusable across multiple unrelated event dispatchers, because that may actually introduce needless dependencies. Instead you can just nest the required delegates.
The way you setup C# events is brutally simple: you declare a delegate that matches a certain event handler signature, and you declare and then raise certain events that expect such and such handlers in their subscribers. Given that the system was designed for industrial production, you can perfectly rely on their performance and behavior.
Hereās an example from the turret behavior code I wrote some time ago
We actually do whatever we want, all the time. Iāve recently implemented a so-called Pulsar system which allows a programmatic access to Update, to which I then hook up (subscribe) listeners from wherever etc. Clearly I didnāt use events for this, even though I could, because I wanted something that is more deliberate and better suited for my particular use case. Once I got this foundation running, I then implemented my own animation tweening engine on top of that. Which means that my entire dynamic, programmatic, configurable tweening functionality stems from just one Update call throughout the entire application. (I will probably share this Pulsar at some point in the future, I was considering that recently.)
If what you made satisfies your requirement, then thereās nothing wrong with it, reinventing wheels is done all the time, and sometimes by doing stuff on your own, youāll find something that actually pushes the envelope or makes your particular project a lot easier to scale up. When someone tells you ādonāt reinvent the wheelā itās typically said in the context of advising you to not shoot yourself in the foot. This is because itās common that rookies will do everything from scratch because they are intimidated by the actual solutions or underestimate the problem space in real production, and not because you explicitly mustnāt stray from the standard. A standard could be too generic or too specific, too oversized or too simplistic. Itās always deeply contextual.
After all, programming is all about a) being practical, b) having a minimally redundant codebase, and c) removing all cognitive strains when reading and parsing the behavior of the code. If you can satisfy all 3 of those, go ahead, have fun and good luck with your project. Even if you made a capital error in your analysis, youāll learn a lot from this, and after a couple of iterations, youāll be able to write a decent event system from nothing, which makes you a better developer overall.