Once compiled, is there a significant difference between if
statements and switch
statements? I wanted to find out. Thanks to /u/sauce-control, I played with this example in the very awesome sharplab.io tool.
Today’s sample program:
class Program
{
private const string STR_HELLO = "Hello";
private const string STR_HELLO_RESPONSE = "Hello to you too!";
private const string STR_GOODBYE = "Goodbye";
private const string STR_GOODBYE_RESPONSE = "Goodbye, see you soon!";
private const string STR_UNKNOWN_RESPONSE = "I don't understand. Sorry.";
static void Main(string[] args)
{
var input1 = Console.ReadLine();
PrintResponse(input1);
PrintResponse_If(input1);
}
private static void PrintResponse(string input1)
{
switch (input1)
{
case STR_HELLO:
Console.WriteLine(STR_HELLO_RESPONSE);
break;
case STR_GOODBYE:
Console.WriteLine(STR_GOODBYE_RESPONSE);
break;
default:
Console.WriteLine(STR_UNKNOWN_RESPONSE);
break;
}
}
private static void PrintResponse_If(string input1)
{
if (input1 == STR_HELLO)
{
Console.WriteLine(STR_HELLO_RESPONSE);
}
else if (input1 == STR_GOODBYE)
{
Console.WriteLine(STR_GOODBYE_RESPONSE);
}
else
{
Console.WriteLine(STR_UNKNOWN_RESPONSE);
}
}
}
Let’s first look at the switch statement IL that is generated.IL for Switch Statement
IL_0000: ldarg.0
// This is interesting - in the switch version, there is a quick
// check to see if the string is null. If it is null, go to the
// default branch first.
IL_0001: brfalse.s IL_0035
// Check to see if the input string == "Hello"
//
IL_0003: ldarg.0
IL_0004: ldstr "Hello"
IL_0009: call bool [mscorlib]System.String::op_Equality(string, string)
IL_000e: brtrue.s IL_001f
// Check to see if the input string == "Goodbye"
//
IL_0010: ldarg.0
IL_0011: ldstr "Goodbye"
IL_0016: call bool [mscorlib]System.String::op_Equality(string, string)
IL_001b: brtrue.s IL_002a
// Unconditionally branch to the default. Why? Both the hello/goodbye
// comparisons use a brtrue to branch if the two strings are equal.
IL_001d: br.s IL_0035
IL_001f: ldstr "Hello to you too!"
IL_0024: call void [mscorlib]System.Console::WriteLine(string)
IL_0029: ret
IL_002a: ldstr "Goodbye, see you soon!"
IL_002f: call void [mscorlib]System.Console::WriteLine(string)
IL_0034: ret
IL_0035: ldstr "I don't understand. Sorry."
IL_003a: call void [mscorlib]System.Console::WriteLine(string)
IL_003f: ret
The switch version uses the string equality operator to see if the strings are equal, then branches accordingly. It also has an early check to see if the string that was passed in is null.IL for if Statement
IL_0000: ldarg.0
IL_0001: ldstr "Hello"
IL_0006: call bool [mscorlib]System.String::op_Equality(string, string)
IL_000b: brfalse.s IL_0018
IL_000d: ldstr "Hello to you too!"
IL_0012: call void [mscorlib]System.Console::WriteLine(string)
IL_0017: ret
IL_0018: ldarg.0
IL_0019: ldstr "Goodbye"
IL_001e: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0023: brfalse.s IL_0030
IL_0025: ldstr "Goodbye, see you soon!"
IL_002a: call void [mscorlib]System.Console::WriteLine(string)
IL_002f: ret
IL_0030: ldstr "I don't understand. Sorry."
IL_0035: call void [mscorlib]System.Console::WriteLine(string)
IL_003a: ret
I was expecting these two versions to be exactly the same. What’s neat about the IF version is that if an equality comparison is false, the method will branch to the next condition to check. This is almost an exact translation of the if-statement code in C# to IL.
Which version is “better“? The code is just about equal in both cases, and when you compare the JIT’d assembly, the difference is even smaller. One version does branch-if-true, one does branch-if-false.
I doubt that one could spot the difference in 99% of scenarios. The only time I see it being less expensive is if you have a lot of null strings to check, but even then you could write an if statement to capture that scenario.