A "Function lookup table" in place of switches
Asked Answered
W

7

7

I came across some code recently that replaces the use of switches by hard-coding a

Dictionary<string (or whatever we would've been switching on), Func<...>> 

and where ever the switch would've been, it instead does dict["value"].Invoke(...). The code feels wrong in some way, but at the same time, the methods do look a bit cleaner, especially when there's many possible cases. I can't give any rationale as to why this is good or bad design so I was hoping someone could give some reasons to support/condemn this kind of code. Is there a gain in performance? Loss of clarity?

Example:

public class A {
    ...
    public int SomeMethod(string arg){
        ...
        switch(arg) {
            case "a": do stuff; break;
            case "b": do other stuff; break;
            etc.
        }
        ...
    }
    ...
}

becomes

public class A {

    Dictionary<string, Func<int>> funcs = new Dictionary<string, Func<int>> {
        { "a", () => 0; },
        { "b", () => DoOtherStuff(); }
        ... etc.
    };

    public int SomeMethod(string arg){
        ...
        funcs[arg].Invoke();
        ...
    }
    ...
}
Wieren answered 10/3, 2011 at 15:27 Comment(4)
Note: you don't need the Invoke after it, just write funcs[arg]().Ricotta
Can you switch on a string? I thought it was only integral types. However, strings can obviously be used as a key in a dictionary.Automat
@Rosarch, yes, you can switch on strings in C#. (Though admitedly C# compiles in a Dictionary inline in the method to implement this, so the switch is actually probably more expensive than this functional approach.)Stoffel
I've seen this technique referred to as an ActionDictionaryPhotographer
R
6

Advantages:

  1. You can change the behaviour at runtime of the "switch" at runtime
  2. it doesn't clutter the methods using it
  3. you can have non-literal cases (ie. case a + b == 3) with much less hassle

Disadvantages:

  1. All of your methods must have the same signature.
  2. You have a change of scope, you can't use variables defined in the scope of the method unless you capture them in the lambda, you'll have to take care of redefining all lambdas should you add a variable at some point
  3. you'll have to deal with non-existant indexes specifically (similar to default in a switch)
  4. the stacktrace will be more complicated if an unhandled exception should bubble up, resulting in a harder to debug application

Should you use it? It really depends. You'll have to define the dictionary at some place, so the code will be cluttered by it somewhere. You'll have to decide for yourself. If you need to switch behaviour at runtime, the dictionary solution really sticks out, especially, if the methods you use don't have sideeffects (ie. don't need access to scoped variables).

Ricotta answered 10/3, 2011 at 15:36 Comment(10)
I 'll disagree with D1: you can bind parameters to match the signature of any method you want to call to the signature of the delegates in the dictionary. You can even do this dynamically if you go to enough trouble.Briny
Could you elaborate on that? Of course, you can use Action delegates and execute the func in that, but then you have no way of capture parameters into it. Second possibility would be object as type, but then you lose compiletime features like type checking and everything is dynamic, introducing a load of problems you didn't have with your switch, problems much more severe than "cluttered and long code".Ricotta
@Femaref: You can have the Action reference variables that have been captured by closure. So you can indirectly "pass parameters" into the Action by later modifying the appropriate variables. Another option is replacing the Actions with "fresher" instances that reference the parameters (again, by closure).Briny
Yes you can, but you'd have to assign the delegates in the scope of the variables to actually capture them by closure, meaning in the method you'd use the construct. So you go a long way of replacing a switch block and removing clutter, just to arrive back and create clutter. this solution has limits and I think you are desperately trying to make it fit, it has its merits but that's about it.Ricotta
@Femaref: You can't use other method signatures with a switch unless you have the parameters to feed to them already in scope, either. In what way would delegates introduce more clutter?Briny
the point is, you don't method signatures with a switch, as you already are in the scope of the method. Given the example from the question, what if case "a" needs variable a, and case "b" needs variable a and b? how do you capture that? In a switch it is simple, the variables are defined in the same method, you can reference them. In the dictionary solution however, you have to make sure to get the variables into the closure. The dictionary solution only works if all cases have the same return type and same parameters. otherwise, use a switch.Ricotta
@Femaref: I really don't know what you 're saying. Pull variables a and b up to where they are visible by the code that populates the dictionary, and reference them inside the delegates that populate it. Due to closure, they need not already have their "final" values.Briny
Of course you can do that, but you want to promote local variables to instance variables just so you can use this idea? I don't feel comfortable with that as instance level variables should have a meaning to the whole instance, and not just be there as helper variables for a single method, even more so as the problem can be solved by something much simpler.Ricotta
I don't think that's "due to closure". Then a and b are just in the same scope as the delegates. True closure would be when a delegate is defined with a and b and the delegate "holds on" to those values even after a and b are no longer in scope. (I think) But that's a separate discussion...Wieren
All of your methods must have the same signature. -- That is actually an advantage. You should be using this style exactly when your methods have the same signature. This has the effect that you don't need to scan the switch for any changes in behavior. You know that everything behaves the same. Normally, someone could sneak in "just the one special case" for option "f" and unless you read the whole long switch, you wouldn't notice it.Pocketknife
B
5

For several reasons:

  1. Because doing it this way allows you to select what each case branch will do at runtime. Otherwise, you have to compile it in.
  2. What's more, you can also change the number of branches at runtime.
  3. The code looks much cleaner especially with a large number of branches, as you mention.

Why does this solution feel wrong to you? If the dictionary is populated at compile time, then you certainly don't lose any safety (the delegates that go in certainly have to compile without error). You do lose a little performance, but:

  1. In most cases the performance loss is a non-issue
  2. The flexibility you gain is enormous
Briny answered 10/3, 2011 at 15:28 Comment(2)
I think that my concerns when looking at this code were based primarily on the fact that you lose some of the control flow analysis and "safety net" of the C# compiler static analysis. I was curious to see if this was something that other people use and acceptWieren
@Roly: This is not only acceptable, but also necessary when you don't know the desired behavior at compile time. And if you think on it, you don't lose any static safety at all. The compiler can and does check that your delegates are of the correct type.Briny
H
2

Jon has a couple good answers. Here are some more:

  • Whenever you need a new case in a switch, you have to code it in to that switch statement. That requires opening up that class (which previously worked just fine), adding the new code, and re-compiling and re-testing that class and any class that used it. This violates a SOLID development rule, the Open-Closed Principle (classes should be closed to modification, but open to extension). By contrast, a Dictionary of delegates allows delegates to be added, removed, and swapped out at will, without changing the code doing the selecting.
  • Using a Dictionary of delegates allows the code to be performed in a condition to be located anywhere, and thus given to the Dictionary from anywhere. Given this freedom, it's easy to turn the design into a Strategy pattern where each delegate is provided by a unique class that performs the logic for that case. This supports encapsulation of code and the Single Responsibility Principle (a class should do one thing, and should be the only class responsible for that thing).
Heterogamy answered 10/3, 2011 at 15:37 Comment(0)
L
1

If there are more number of possible cases then it is good idea to replace Switch Statement with the strategy pattern, See this.

Applying Strategy Pattern Instead of Using Switch Statements

Latisha answered 10/3, 2011 at 15:35 Comment(1)
This is the Strategy Pattern.Beata
E
1

No one has said anything yet about what I believe to be the single biggest drawback of this approach.

It's less maintainable.

I say this for two reasons.

  1. It's syntactically more complex.
  2. It requires more reasoning to understand.

Most programmers know how a switch statement works. Many programmers have never seen a Dictionary of functions.

While this might seem like an interesting and novel alternative to the switch statement and may very well be the only way to solve some problems, it is considerably more complex. If you don't need the added flexibility you shouldn't use it.

Engaged answered 18/7, 2011 at 19:59 Comment(1)
This is an interesting argument - how low should you go? Should you only use if ... else if ... else statements because there must certainly be some programmers out there who have not seen a switch? I think this is a problematic argument to make. Instead of appealing to the lowest common denominator you should rather educate people to the better approaches.Pocketknife
P
0

Convert your A class to a partial class, and create a second partial class in another file with just the delegate dictionary in it.

Now you can change the number of branches, and add logic to your switch statement without touching the source for the rest of your class.

Photographer answered 10/3, 2011 at 15:40 Comment(0)
H
0

(Regardless of language) Performance-wise, where such code exists in a critical section, you are almost certainly better off with a function look-up table.

The reason is that you eliminate multiple runtime conditionals (the longer your switch, the more comparisons there will be) in favour of simple array indexing and function call.

The only performance downside is you've introduced the cost of a function call. This will typically be preferable to said conditionals. Profile the difference; YMMV.

Hardly answered 21/3, 2022 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.