Switch is EVIL! (Part 3b)

Its only fair that we did deworming of switch-case/if-else ladder from Java code, we should have equivalent C# code. But let’s take this opportunity to explore what .NET/C# have to offer in terms of deworming tools… like delegates, lambdas, Func<> and Action<>

Let's look at the beast
using System;
using System.Text;
class Evil
{
string changeCase(string caseType, params string[] words)
{
// Sanitize the input
caseType = caseType.Trim().ToLower();
// Accumulate result in StringBuilder
var result = new StringBuilder();
// We currently support lower and camel case
if (caseType == "lower")
{
// Every word is converted to lower
// And concatenated
foreach (var word in words)
{
result.Append(
word
.Trim()
.ToLower());
}
}
else if (caseType == "camel")
{
// First word is lower
result.Append(
words[0]
.Trim()
.ToLower()
);
// Subsequent words are Proper case
for (var offset = 1;
offset < words.Length;
offset++)
{
// First letter is uppercase
result.Append(
words[offset]
.Substring(0, 1)
.ToUpper());
// Rest of the letters are lowercase
result.Append(
words[offset].
Substring(1).
ToLower());
}
}
return result.ToString();
}
static void Main(string[] args)
{
Evil obj = new Evil();
Console.WriteLine(obj.changeCase("camel", "Two", "Words"));
}
}
view raw NetSwitch.cs hosted with ❤ by GitHub

The story is the same in all programming languages. Developers tend to use switch-case or if-else ladders as we were probably “taught” to use them when we started learning programming. It’s deeply ingrained into our brains, to the extent that we justify it as being simple.
I hear a a lot of people describe use of maps to eliminate switch as “OVER-ENGINEERING”.

So, we can use interface in C# (like we did with Java in Part 3a), but we could use something else this time… maybe delegate, lambda or Func<>

Get ready to Func
/**
* Func, Action and Predicate are generic deleagtes
* Action<int> -> takes one parameter and returns void
* Func<string, int> -> takes one parameter string and returns int
* Predicate<int> -> takes one parameter int and returns bool
*
* using in C# allows us to "typedef" a complex type
*/
using ConverterFunction = System.Func<string[], string>;
view raw TypeDef.cs hosted with ❤ by GitHub
How do we create a map of funcs?
/**
* Maps are called Dictionary in .NET
* We are creating a map between
* string -> "camel"
* Func<> -> camelConverter function
*/
Dictionary<string, ConverterFunction> converters =
new Dictionary<string, ConverterFunction>
{
{"lower", lowerConverter},
{"camel", camelConverter}
};
view raw Dictionary.cs hosted with ❤ by GitHub
We are set.. onward to refactoring
/**
* changeCase now uses converter Dictionary
* If a converter is found, it invokes it
* or else returns null
*/
public string changeCase(string caseType, params string[] words)
{
// Sanitize the input
caseType = caseType.Trim().ToLower();
// Lookup the converter function in dictonary
ConverterFunction converter;
converters.TryGetValue(caseType, out converter);
if (converter !=null)
return converter(words);
return null;
}
Now it's just a matter of creating converters
static string camelConverter(string[] words) {
var result = new StringBuilder();
// First word is lower
result.Append(
words[0]
.Trim()
.ToLower()
);
// Subsequent words are Proper case
for (var offset = 1;
offset < words.Length;
offset++)
{
// First letter is uppercase
result.Append(
words[offset]
.Substring(0, 1)
.ToUpper());
// Rest of the letters are lowercase
result.Append(
words[offset].
Substring(1).
ToLower());
}
return result.ToString();
}
static string lowerConverter(params string[] words) {
// Accumulate result in StringBuilder
var result = new StringBuilder();
// Every word is converted to lower
// And concatenated
foreach (var word in words)
{
result.Append(
word
.Trim()
.ToLower());
}
return result.ToString();
}
view raw Converters.cs hosted with ❤ by GitHub
It's all over... not!

In all the examples we have seen (JavaScript, Java and in this C# code), we have only used converter that are pre-defined. Which makes it difficult to add new converters at runtime. This means our code will have to be modified to include more converters. So what’s the use of eliminating switch-case if we still have to :manually” add the converter to the dictionary? Good point!

Let’s make our code flexible, so that apart from built in converters, we can add more converters at runtime WITHOUT having to modify our existing code.

It’s easy! Add two methods to our class which can add/remove converters.

Dynamism to the max!

Let’s first add a method to our Converter class, which will add more entries to the dictionary

/**
* Dynamically adding more converters
*/
public void AddConverter(string caseType, ConverterFunction func)
{
// That's it!
converters[caseType] = func;
}
Flexible client class
/**
* Client class can add a new converter
* either defined by itself or from some other source
*/
class ClientClass
{
// Snake case is very polular
// Where else? Python 🙂
static string snakeConverter(params string[] words)
{
// snake_case_is_separated_by_underscore
var result = new StringBuilder();
// Convert each word to lower
// and separate using underscore
for (var offset = 0;
offset < words.Length1;
offset++)
{
result.Append(
words[offset]
.Trim()
.ToLower()
);
result.Append("_");
}
// Add the last word without _
result.Append(
words[words.Length 1]
.Trim()
.ToLower()
);
return result.ToString();
}
static void Main(string[] args)
{
var obj = new Angel();
// Add our own converter and call it
// No need to modify the coverter class
obj.AddConverter("snake", snakeConverter);
Console.WriteLine(obj.changeCase("snake", "Two", "Words"));
}
}

That’s it! We have created a Case Converter class which has a few built-in converters, like lower and camel, but we have also made it extensible, so that others can provide “plugins” for our class. third-party developers could come up with their own converters which the client can use plugin and call.

Here is the final code
using System;
using System.Collections.Generic;
using System.Text;
using ConverterFunction = System.Func<string[], string>;
class Angel
{
static string camelConverter(string[] words)
{
var result = new StringBuilder();
// First word is lower
result.Append(
words[0]
.Trim()
.ToLower()
);
// Subsequent words are Proper case
for (var offset = 1;
offset < words.Length;
offset++)
{
// First letter is uppercase
result.Append(
words[offset]
.Substring(0, 1)
.ToUpper());
// Rest of the letters are lowercase
result.Append(
words[offset].
Substring(1).
ToLower());
}
return result.ToString();
}
static string lowerConverter(params string[] words)
{
// Accumulate result in StringBuilder
var result = new StringBuilder();
// Every word is converted to lower
// And concatenated
foreach (var word in words)
{
result.Append(
word
.Trim()
.ToLower());
}
return result.ToString();
}
Dictionary<string, ConverterFunction> converters =
new Dictionary<string, ConverterFunction>
{
{"lower", lowerConverter},
{"camel", camelConverter}
};
public string changeCase(string caseType, params string[] words)
{
// Sanitize the input
caseType = caseType.Trim().ToLower();
ConverterFunction converter;
converters.TryGetValue(caseType, out converter);
if (converter != null)
return converter(words);
return null;
}
public void AddConverter(string caseType, ConverterFunction func)
{
converters[caseType] = func;
}
}
class ClientClass
{
static string snakeConverter(params string[] words)
{
// snake_case_is_separated_by_underscore
var result = new StringBuilder();
// Convert each word to lower
// and separate using underscore
for (var offset = 0;
offset < words.Length1;
offset++)
{
result.Append(
words[offset]
.Trim()
.ToLower()
);
result.Append("_");
}
// Add the last word without _
result.Append(
words[words.Length 1]
.Trim()
.ToLower()
);
return result.ToString();
}
static void Main(string[] args)
{
var obj = new Angel();
obj.AddConverter("snake", snakeConverter);
Console.WriteLine(obj.changeCase("snake", "Two", "Words"));
}
}

Are you now convinced that ALL switch MUST DIE! ?

Switch is EVIL Series

  1. Switch is EVIL! (Part 1) – Convert it to arrays
  2. Switch is EVIL! (Part 2) – Convert it to maps
  3. Switch is EVIL! (Part 3) – Create function maps
  4. Switch is EVIL! (Part 3a) – Implementing in Java
  5. Switch is EVIL! (Part 3b) – Implementing in C#

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s