The C# classes I am talking about are only classes that do not inherit Monobehavior. Subscribe to events in the constructor and unsubscribe in the finalizer? In addition, if my list still holds an instance of this C# object, does it make sense to unsubscribe like this? I almost confuse the C# way of writing the observer pattern with += and -=.
You subscribe and unsubscribe if/when it makes sense for your program. If you have a class that has an event (producer) and a class that is subscribed to that event (consumer), then you can subscribe whenever you want to start having a method in that class called when the event is invoked. If this is from the moment the class is created then you subscribe in the constructor.
You unsubscribe when you don’t want the relevant method to execute anymore when the event is raised.
Unsubscribing in the finalizer is pointless. The moment a consumer subscribes to a producer, then this producer holds a reference to that consumer, therefore the finalizer won’t run as long as the consumer is subscribed. The finalizer will only run when there are no more references to that class that through the chain of references lead to a root object.
Basically this means that as long as the consumer is subscribed to the producer, if there are no other references to the consumer then its lifetime is the lifetime of the producer.
I don’t understand this quote, what list? and are we talking about the consumer object? If we are talking about the consumer then with or without a reference from a list, unsubscribing from a finalizer doesn’t make any sense, for the reasons I described.
Finally, all this is true for instance events. If we are talking about static events then you need to treat them, for domain reload purposes, the same way as other static variables.
You can use the constructor for subscribing, and the Dispose pattern for unsubscribing:
public sealed class FootstepSoundHandler : IDisposable
{
readonly Walker walker;
readonly AudioSource audioSource;
public FootstepSoundHandler(Walker walker, AudioSource audioSource)
{
this.walker = walker;
this.audioSource = audioSource;
walker.Footstep += OnFootstep;
}
public void Dispose() => walker.Footstep -= OnFootstep;
void OnFootstep() => audioSource.Play();
}
public class Walker : MonoBehaviour
{
public event Action Footstep;
[SerializeField] AudioSource audioSource;
readonly FootstepSoundHandler footstepSoundHandler = new(this, audioSource);
void OnDestroy() => footstepSoundHandler.Dispose();
}
IDisposable.Dispose
is basically the C# equivalent of MonoBehaviour.OnDestroy
. A class implementing IDisposable communicates that whoever creates an instance of the object, should also make sure to call Dispose on it once it’s no longer needed.
In unity I would use Onenable and OnDisable for pub/sub. Though sometimes you want to receive the events even if you are disabled like a minimized in game UI, then awake/start / ondestroy is better.
Edit: I often see people put commands on the event bus, I think this is a bad habit. Only but events on the bus and commands through some other pattern like a command pattern / singleton etc