Search code examples
c#dictionaryunity-game-enginelisteneraction

How to register listener to Actions stored in a Dictionary


For a game I'm developing I'm keeping track of a GameState to determine which systems should be active. To enable systems to register themselves to State changes, I've written the following code:

    public static Action<State> OnDefaultStateChange;
    public static Action<State> OnConstructionStateChange;

    private static Dictionary<GameState, Action<State>> _stateChangeActions =
        new Dictionary<GameState, Action<State>>()
        {
            {GameState.Default, OnDefaultStateChange},
            {GameState.Construction, OnConstructionStateChange}
        };

When a state is changed, it invokes the relevant action by looking up the GameState key in the _stateChangeActions dictionary. Here's the strange behaviour that I can't understand. If I subscribe to the action by using _stateChangeActions[key] += ListenerMethod;, it invokes correctly. But if I subscribe on the public static field, e.g OnDefaultStateChange += ListenerMethod;, and I invoke the action through the dictionary, it's as if there are no listeners.

I haven't been able to find out why this happens. Note: I'm using Unity Engine, and this issue isn't blocking me, I'm just curious.


Solution

  • Answer to your question

    OnDefaultStateChange and _stateChangeActions have no relation to each other, other than the fact you use OnDefaultStateChange to initialize _stateChangeActions. Your line with {GameState.Default, OnDefaultStateChange}, adds the object inside OnDefaultStateChange to the dictionary and not the reference, which means that _stateChangeActions[GameState.Default] is not the same as OnDefaultStateChange.

    An example to show what is actually going on in your setup:

    var state = new { LivesLeft = 2, ShirtColor = "brown" };
    
    // Corresponds to 'OnDefaultStateChange'
    Action<State> someAction = (s) =>
    {
        Console.WriteLine("Lives: " + s.LivesLeft);
    };
    
    // Corresponds to '_stateChangeActions'
    Action<State> copyOfSomeAction = someAction;
    
    // Subscribe to "OnDefaultStateChange"
    someAction += (s) =>
    {
        Console.WriteLine("Shirt color: " + s.ShirtColor);
    };
    
    // 'someAction' is longer equal to 'copyOfSomeAction' since 'someAction'
    // has been replaced with a new Action which produces the result from two other
    // Actions.
    
    someAction(state);
    // Output:
    // Lives: 2
    // Shirt color: brown
    
    copyOfSomeAction(state);
    // Output:
    // Lives: 2
    

    As you can see OnDefaultStateChange and _stateChangeActions works as two independent objects, so "subscribing" to OnDefaultStateChange doesn't make that new subscriber available to _stateChangeActions.

    How to solve your issue

    I would suggest you make use of the event features in C#. I'm guessing a little on how you actually check the type of event to fire, but your event handling class could look something like this:

    // MyEventHandlerClass.cs
    public delegate void StateChangedEventHandler(object sender, State state);
    public static event StateChangedEventHandler DefaultStateChanged;
    public static event StateChangedEventHandler ConstructionStateChanged;
    
    private static FireNewStateChangeEvent(State state) {
        switch (state.StateChangeType)
        {
            case GameState.Default:
                DefaultStateChanged.Invoke(this, state);
            case GameState.Construction:
                ConstructionStateChanged.Invoke(this, state);
        }
    }
    

    To subscribe to events you simply do pretty much like you already do:

    MyEventHandlerClass.DefaultStateChanged += ListenerMethod;
    

    With this setup you can subscribe or unsubscribe (-=) to events from wherever.