Alternative to switch with non-constant
Asked Answered
K

5

9

I just learned that a switch statement can't use non-constant conditions. Which is fine and all, I get it. But does that really mean I have to make a big if-else block? It's so ugly I'm crying.

Some context: I'm doing a Unity project and I want to switch on the current animation state. A good way to check the current animation state is to compare hashes, which means I need to calculate the hashes for the animation state. After calculating them I want to switch on them. (Writing this I realized I can paste the resulting hash into a constant, but now I still want an answer)

int state1 = Animator.StringToHash("State1");
int state2 = Animator.StringToHash("State2");
int hash = _myAnimator.GetCurrentAnimatorStateInfo(0).shortNameHash;
switch (hash):
{
case state1:
    //DoStuff
    break;
case state2:
    //Other stuff
    break;
}

What's the best way to do this?

Kochi answered 20/12, 2015 at 23:52 Comment(2)
Are hashes in your context even unique? Seems dangerous.Sermon
Why not define your state as an Enum or even simply an int. What's the reason to hash the state? States should be pretty well defined, especially if you're writing a switch statement for it.Susurrate
S
8

You can do this with a dictionary.

Try this:

int state1 = Animator.StringToHash("State1");
int state2 = Animator.StringToHash("State2");
int hash = _myAnimator.GetCurrentAnimatorStateInfo(0).shortNameHash;
var cases = new Dictionary<Func<bool>, Action>()
{
    { () => hash == state1, () => { /* Do stuff */} },
    { () => hash == state2, () => { /* Do other stuff */} },
};

cases
    .Where(c => c.Key()) // find conditions that match
    .Select(kvp => kvp.Value) //select the `Action`
    .FirstOrDefault() // take only the first one
    ?.Invoke(); // Invoke the action only if not `null`

To make it a little more clean you could define a Switch class like this:

public class Switch : IEnumerable<Switch.Case>
{
    private List<Case> _list = new List<Case>();

    public void Add(Func<bool> condition, Action action)
    {
        _list.Add(new Case(condition, action));
    }

    IEnumerator<Case> IEnumerable<Case>.GetEnumerator()
    {
        return _list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _list.GetEnumerator();
    }

    public void Execute()
    {
        this
            .Where(c => c.Condition())
            .Select(c => c.Action)
            .FirstOrDefault()
            ?.Invoke();
    }

    public sealed class Case
    {
        private readonly Func<bool> _condition;
        private readonly Action _action;

        public Func<bool> Condition { get { return _condition; } }
        public Action Action { get { return _action; } }

        public Case(Func<bool> condition, Action action)
        {
            _condition = condition;
            _action = action;
        }
    }
}

Then the code looks like this:

int state1 = Animator.StringToHash("State1");
int state2 = Animator.StringToHash("State2");
int hash = _myAnimator.GetCurrentAnimatorStateInfo(0).shortNameHash;
var @switch = new Switch()
{
    { () => hash == state1, () => { /* Do stuff */} },
    { () => hash == state2, () => { /* Do other stuff */} },
};

@switch.Execute();

And if you write it like this it almost looks like a normal switch statement:

var @switch = new Switch()
{
    {
        () => hash == state1,
        () =>
        {
            /* Do stuff */
        }
    },
    {
        () => hash == state2,
        () =>
        {
            /* Do other stuff */
        }
    },
};
Susannsusanna answered 21/12, 2015 at 2:38 Comment(0)
I
6

You can also use case guards and local functions like this:

bool HashMatches(int TargetHash) => hash == TargetHash;

switch (true):
{
case true when HashMatches(state1):
    //DoStuff
    break;
case true when HashMatches(state2):
    //Other stuff
    break;
}
Isfahan answered 10/5, 2022 at 22:12 Comment(0)
A
1

Whether you can simplify it or not, it depends on the similarities between your "DoStuff", "Other Stuff", "Next Stuff", and "You other stuffs"

  1. Suppose your Stuff "family members" are actually:

    int stuffAction(int state){
        int modified_state;
        //do something on state and modified state
        return modified_state;
    }
    

    Then, obviously you Stuffs can be simplified by using function, just as shown above. It can be simplified likewise as long as your Stuff have same function with different argument.

  2. Also, if you Stuffs are in the form different functions but having the same input parameters, you can create Dictionary of delegates (see System.Collections.Generic.Dictionary<string, System.Delegate>) such that when you can call the Stuff you simply need to do

    dic[state](input parameters here)
    

    instead of using if-else or switch

There might be some possibilities where your code cannot be simplified further, but the bottom line is, as I said earlier, depend on the similarities between your Stuffs.

Aribold answered 21/12, 2015 at 2:5 Comment(0)
L
0

You can do it only with if-else if:

int state1 = Animator.StringToHash("State1");
int state2 = Animator.StringToHash("State2");
int hash = _myAnimator.GetCurrentAnimatorStateInfo(0).shortNameHash;
if (hash == state1) {
    //DoStuff
}
else if (hash == state2) {
    //Other stuff
}
Laconism answered 21/12, 2015 at 0:11 Comment(1)
OP specifically mentioned not wanting to have a 'big if-else block' which is what this amounts to. Personally I don't see the issue and would probably do it this way anyway.Zito
U
0

You can use the conditional operator to accomplish a pseudo switch statement, though it is actually an if-else block. This assumes your "do stuff" sections can be wrapped in methods returning a common type. Assuming DoState1 and DoState2 both return void...

hash == state1 ? DoState1()
: hash == state2 ? DoState2()
: throw new NotImplementedException();

if you wanted to return a value... change the Do methods to return the same type (e.g. bool) and assign it to a variable.

bool result 
  = hash == state1 ? DoState1()
  : hash == state2 ? DoState2()
  : false;

It feels and reads very much like a switch statement or expression. The only limitation is you must encapsulate your code block into a method, which is a good practice anyway.

This approach is also great for LINQ as it converts to a SQL CASE statement (for EFC7+ and SQL Server)

.Select(x => new Dto
{
   RecordExists = x.Source.RecordExists is null ? "UNKNOWN"
                : x.Source.RecordExists > 1 ? "MANY"
                : x.Source.RecordExists == 1 ? "ONE"
                : "NO"
});
Unwilled answered 25/9, 2023 at 20:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.