Hey there!
I often have the case that several listeners need to react to an event but do not need the same arguments. I could just ignore the arguments in the listener’s methods that do not need them but this just feels wrong. Or is it the proper way to do it?
I could also duplicate the event and have them specialized with their listener’s methods arguments but this would increase the chances of doing mistakes.
Any thoughts?
Thanks! Here is a minimal example:
public class MyClass : MonoBehaviour
{
public static event Action<Vector3, Quaternion, int, Transform> OnSomething;
private void Update()
{
// Call event on a condition with some arguments
// (this one is silly, just for the example)
if (Time.time > 10.0f)
OnSomething?.Invoke(transform.position, transform.rotation, 42, transform.GetChild(0));
}
}
public class ListenerA : MonoBehaviour
{
private void OnEnable()
{
MyClass.OnSomething += ActionA;
}
private void ActionA(Vector3 v, Quaternion q, int i, Transform t)
{
// DOES use the arguments
float result = v.x + q.eulerAngles.x + i + t.position.magnitude;
Debug.Log("The result is: " + result);
}
}
public class ListenerB : MonoBehaviour
{
private void OnEnable()
{
MyClass.OnSomething += ActionB;
}
private void ActionB(Vector3 v, Quaternion q, int i, Transform t)
{
// Does NOT use the arguments, they are ignored.
// Is there a way to avoid this?
Debug.Log("I'm done.");
}
}
Events are delegates, which are types, and you cannot overload types, only methods. So, it’s not possible to subscribe to a single event with different arguments. That said, there are three approaches you can take to address this.
The first two are the ones you already mentioned:
- Use separate events for different purposes. This approach works well when the events represent conceptually distinct actions.
- Ignore some arguments. While this might technically work, as you noted, it often “feels wrong” and can lead to less intuitive code.
The third option, and my personal favorite when the events are not conceptually different, is to create an event with one argument: a type that “wraps” all your arguments. For instance, in your example, you could define a MovementInfo
type to encapsulate the three arguments. This approach offers two advantages:
- Simplified Refactoring
By using a wrapper type, you reduce the need for extensive refactoring when adding or removing arguments. If each method in your codebase accepts a MovementInfo
object, you can modify its fields without having to update method signatures throughout your project.
- Improved Conceptual Clarity
This approach feels more natural because the arguments are grouped together in a meaningful way. They form a logical unit, similar to how a Transform
object might contain Position
, Rotation
, and Scale
. While different methods might only use certain fields (e.g., Transform.Position
or Transform.Rotation
), it still feels intuitive because these fields belong together. Even if you later decide to use Transform.Scale
, you won’t need to change any method signatures since it’s already part of the Transform
type.
The same concept applies when creating a type that groups your required arguments, such as a MovementInfo
type. You can use only some of its fields initially and later add new fields to the type as needed.
1 Like
Thanks! Seems like a good practice indeed
Do you know if there is an extra cost when passing arguments that correspond to a large quantity of memory? I’m thinking of a large texture or even an array of textures.
Because if it does, then maybe relying on separate events might be useful in such a case (when one listener would not use this “costly” argument).
In general, you shouldn’t worry about performance issues until you encounter actual performance problems. That said, when it comes to passing arguments by reference (whether through reference types or value types using the ref
keyword), the performance impact is negligible because you’re not passing the entire object, only a reference to the memory it occupies.
This is a broad and very important topic. To deepen your understanding, you should explore documentation, videos, and tutorials on C# that cover reference types, value types, and the differences between passing arguments by value and by reference.
1 Like