Long functions can make code difficult to read. We have to use mental energy to remember what was done in the code above the line we’re looking at.
Replacing long “switch” statements with a dictionary may make your code shorter and easier to work with.
In the sample code, we’re going to handle keyboard input (the WASD keys) changing the location of something on the screen. We’ll use this simple Location class with XPosition and YPosition properties.
namespace Workbench.Models; public class Location { public int XPosition { get; set; } public int YPosition { get; set; } public Location(int xPosition, int yPosition) { XPosition = xPosition; YPosition = yPosition; } }
With the switch statement version of the code, in the window’s constructor, we initialize a Location object and store it to a class-level backing variable.
Starting on line 21, we have the function that handles key presses.
There’s a switch statement starting on line 23 with a “case” for each key/letter we want to handle. Inside the case blocks, we call functions that change the properties on the _currentLocation variable (checking that we don’t go beyond the screen boundaries).
By having the case blocks call function, instead of having all the code inside the case block, we’ve already simplified and shortened the switch statement, making it easier to read. However, we can go even farther.
using System.Windows; using System.Windows.Input; using Workbench.Models; namespace Workbench; public partial class MainWindow : Window { private const int MAX_X_POSITION = 500; private const int MAX_Y_POSITION = 500; private readonly Location _currentLocation; public MainWindow() { InitializeComponent(); _currentLocation = new Location(100, 100); } private void MainWindow_OnKeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.W: MoveUp(); break; case Key.A: MoveLeft(); break; case Key.S: MoveDown(); break; case Key.D: MoveRight(); break; } e.Handled = true; } private void MoveUp() { if (_currentLocation.YPosition > 0) { _currentLocation.YPosition--; } } private void MoveLeft() { if (_currentLocation.XPosition > 0) { _currentLocation.XPosition--; } } private void MoveDown() { if (_currentLocation.YPosition < MAX_Y_POSITION) { _currentLocation.YPosition++; } } private void MoveRight() { if (_currentLocation.XPosition < MAX_X_POSITION) { _currentLocation.XPosition++; } } }
Since we each case block’s code inside a function, we can make this even shorter.
We start by creating another class-level variable – the _keyPressActions variable on lines 16-17. This is a dictionary. The key of the dictionary is the datatype of the value we used for the switch statement. In this case, System.Windows.Input.Key. The value datatype of the dictionary is a System.Action – a delegate to our functions.
Inside the constructor, we populate the dictionary with key value pairs, using the keypress we want to handle as the entry’s key and the name of the function to run for that key as the dictionary entry’s value. For example, on line 24, we add an entry for a keypress of “W” to be associated with the function “MoveUp”.
The new function to handle key presses is on lines 32 to 38.
On line 35, we get the get the value from the dictionary whose key matches the key the user press, then invokes the function.
The “?” is a “null conditional operator“. If there is no dictionary entry whose key matches the key that was pressed, the program does not try to run the rest of the line – which would get a null-reference exception. It only tries to run the Invoke() if the dictionary had an entry for the key value.
Now, if we want to handle another key pressed by the user, we add a new entry to the dictionary in the constructor.
using System; using System.Collections.Generic; using System.Windows; using System.Windows.Input; using Workbench.Models; namespace Workbench; public partial class SecondWindow : Window { private const int MAX_X_POSITION = 500; private const int MAX_Y_POSITION = 500; private readonly Location _currentLocation; private readonly Dictionary<Key, Action> _keyPressActions = new Dictionary<Key, Action>(); public SecondWindow() { InitializeComponent(); // Setup dictionary of functions to run for key presses _keyPressActions.Add(Key.W, MoveUp); _keyPressActions.Add(Key.A, MoveLeft); _keyPressActions.Add(Key.S, MoveDown); _keyPressActions.Add(Key.D, MoveRight); _currentLocation = new Location(100, 100); } private void SecondWindow_OnKeyDown(object sender, KeyEventArgs e) { // Run action from dictionary, if there is an action for the key _keyPressActions[e.Key]?.Invoke(); e.Handled = true; } private void MoveUp() { if (_currentLocation.YPosition > 1) { _currentLocation.YPosition--; } } private void MoveLeft() { if (_currentLocation.XPosition > 1) { _currentLocation.XPosition--; } } private void MoveDown() { if (_currentLocation.YPosition < MAX_Y_POSITION) { _currentLocation.YPosition++; } } private void MoveRight() { if (_currentLocation.XPosition < MAX_X_POSITION) { _currentLocation.XPosition++; } } }