…or how to switch on a type in C#.
There’s a method in one of Seed’s controller type classes that’s nearly 1000 lines long. How did it get like that? Well basically it’s one big if-then-else construct because C# can’t switch on the type of an object.
Steady on old chap! This article was written many years ago, as from C# version 7 it can switch on type. That doesn’t mean this technique isn’t still valuable, but it’s no longer necessary for a simple switch on type.
So that old Seed method looked pretty much like this:
if(curryObject is MasalaDosa) { //a few lines of code }else if (curryObject is VegetableThali) { //a few lines of code }else if (curryObject is TandooriMas) { //a few lines of code } //etc. etc.
The real problem with it isn’t the fact that it’s one giant if-then-else structure it’s that each few lines of code is a few too many. It should really be more of the form:
if(curryObject is MasalaDosa) HandleMasalaDosa((MasalaDosa)curryObject); else if (curryObject is VegetableThali) HandleVegetableThali((VegetableThali)curryObject); else if (curryObject is TandooriMas) HandleTandooriMas((TandooriMas)curryObject); //etc. etc.
Naturally you’d think you could use polymorphism to get around the problem;
HandleCurry(MasalaDosa curryObject) {} HandleCurry(VegetableThali curryObject) {} HandleCurry(TandooriMas curryObject) {} //etc. etc.
In reality though sometimes you can’t or sometimes it’s just more practical to use a switch type of affair. As mentioned earlier though you can’t switch on the type of an object in C# you can only switch on type if you’re using C~ version 7 or later. Alternatively you can use a lookup table (actually a dictionary in this case);
class CurryHandler { Dictionary<type, action<curry="">> actionTable;</type,> public CurryHandler() { actionTable = new Dictionary<type, action<curry="">> { {typeof(MasalaDosa), new Action(HandleMasalaDosa)}, {typeof(VegetableThali), new Action(HandleVegetableThali)}, {typeof(TandooriMas), new Action(HandleTandooriMas)} }; }</type,> public void HandleCurry(Curry curry) { actionTable[curry.GetType()](curry); } //etc. etc.
In C#, Action is a generic type that represents a method, thus Action<Curry> is a type that represents a method that takes a “Curry” as a parameter. So what we’re doing here is constructing a dictionary that links a Type to an Action.
So when we call actionTable[curry.GetType()](curry); what we’re really saying is; get the type of “curry” and look that up in the dictionary, then take the associated Action and call that method with the “curry” object as its parameter.
I rather like this pattern, once you understand it I think it’s very clear, more so than a switch statement actually because it gives you a simple reference from Type to Action, you don’t have to wade through a ton of case whatever: do_something(); break; or worse if(whatever is someType)…else if (whatever is someOtherType)…
Naturally you don’t have to use a Type as the key, you could use any object as the lookup. You can also use lambda expressions rather than declaring a bunch of new Action… but it’s all too tempting then to write code in the definition of the lookup table and make things messy and unreadable.
It’s not without problems though. As coded above though there’s no protection. If I add a new type of Curry, say DhalMakhani and call HandleCurry with that a KeyNotFoundException will be thrown. We can avoid this easily though and mimic the default: behaviour of the switch statement;
public void HandleCurry(Curry curry) { Action curryAction; if(actionTable.TryGetValue(curry.GetType(), out curryAction)) curryAction(curry); //else //do whatever we'd do in default: } //etc. etc.
I’ve also never checked how fast it is. It could be much slower than the equivalent if-then-else construct.
One possibly advantageous side effect however is that the logic has been moved out of the programming space into the data space. What do I mean by that? Well actionTable is a variable, you can change it. You can add Actions, take them away or change them at run time. This allows you to do some really clever things but should also be ringing some rather large warning bells. With great power comes great responsibility and the temptation to write code that is simply horrible to read and debug must be avoided, even if what it’s doing is really, really cool.
No doubt this is a clever technique, but it’s far from new. For my next trick I’ll explain how this basic technique can be traced back several decades into early digital electronics.