Tag Archives: ILDASM

Behind the scenes: Switch statement

Today we are going to look at one of C#’s language features, namely the Switch statement. Though this may seem straight forward, you may be surprised by the internal workings of this seemingly ‘trivial’ operation.

There are different ways of switching, let’s look at the most trivial one of them first.

public static void SwitchSequence(int x)
{
      switch (x)
      {
       case 1: Console.WriteLine("one");
       break;

       case 2: Console.WriteLine("two");
       break;

       case 3: Console.WriteLine("three");
       break;
    }
}

Understanding this is basic, but let’s look at what the compiler generated using IL DASM.

As you can clearly notice the br.s is our check command, to see if the input matches the possible case. Next you have IL_0019, IL_0026, IL_0033 that respectively refer to case 1, 2, 3 and point to the location to load the string according to that case before printing it (which is obviously a void operation, it does not return anything).

When we look to a more complex switch-statement thought, this is, a switch where the case numbers do not follow up in a nice sequential order with gaps here and there, the IL code starts to look mutated.

public static void SwitchNoSequence(int x)
{
      switch (x)
       { 
         case 11: Console.WriteLine("Eleven");
         break;

         case 14: Console.WriteLine("Fourteen");
         break;

         case 17: Console.WriteLine("Seventeen");
         break;
       }   
}

This will already make the IL code look slightly different:

This looks quite a bit different from what we had earlier.

The most frequent new instruction here is first located at “IL_0006” beq.s, a branch-equal command. This is comparably with the “If- else if” commonly found in programming languages.

What happens when the switch-casing does not follow up in a sequential order is the switch being translated to the If-else if structure we see here, done by the compiler.

So we could actually write this same piece of code using just If-Else if in C#.

This would give us this:

public static void SwitchNoSequence(int x)
{

    if (x == 11)
    {
        Console.WriteLine("Eleven");
    }
    else if (x == 14)
    {
        Console.WriteLine("Fourteen");
    }
    else if (x == 17)
    {  
        Console.WriteLine("Fourteen");
    }
}

Using a switch is much more convenient though, especially for a larger number of possible entries.

And finally we’ll look at a switch using strings instead of integers.

Let’s take the following piece of code:

public static void SwitchString(string s)
{
    switch (s)
    { 
       case "Hello":
           Console.WriteLine("Hello there");
           break;

       case "World":
           Console.WriteLine("Well hello world");
           break;
    }   
}

You have string input, which will then be checked against the different options in the switch case, and Console.WriteLine will be called if it matches.

After we used IL DASM again, this is what we get.

Bool [mscorlib]System.String::op_Equality(string,string) is the new function that gets called here. Obviously is the Equals method on the string, which returns a Boolean value (either true or false). If the first Boolean check comes back as true, it will branch of to instruction IL_0022 where we find the string that has to be printed using Console.WriteLine();

This way of dealing with switches is quite similar to the previous one, where we branched off after an integer check.

Digging deeper into C#

Let's talk about IL or intermediate language code. When you run a C# program the code will get ‘transformed’ into IL. IL is coding that looks alot more like native machine code, however there are some differences to be noted.

  • Each instruction represents exactly one fundamental operation
  • Control flow information may not be included in the instruction set
  • Registers can be large to limitless.

This code can be handwritten, but I’m not going in on that aspect of programming right now.
Whenever you execute a program in C# the conversion to IL is an automatic step during execution (the right way of naming this would be compiling, your code get’s compiled into assemblies which are containers for IL code).

To truly understand how a language works you have to dig deeper than what you see in your IDE.
So let’s go ahead and take a step into the dark.

Let’s fire up Visual C# and make a console application.
We’re going to build a simple calculator and examine that code afterwards.

 

 

 

 

 

 

 

 

This is what the coding looks like to you, it’s very readable code (assuming you’ve got a knowledge of C#). Yet  this is not at all what your machine get’s to deal with. Once you press that debug button alot is going on that’s not visually represented by Visual Studio. To go and look how this code will look like in IL we’re going to use ILDASP, the intermediate language disassembler.

Load up your program and press CTRL+O and navigate to where you saved your application.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

In this case my application was called ILTranslation.exe but the name doesn’t matter (obviously). Go ahead and double click it and the file will get loaded into ILDASM.

Once you’ve done that just click on all the ‘+’ signs in the treeview you get.

 

 

 

 

 

 

 

 

 

Here you get a view of seperate IL pieces that live inside the program you just created.
As you see the Calculator class that we made is a seperate part inside this treeview with subcategories for the Add function we called in.

Just by taking a swift look at this you should be able to recognize pieces of code that you wrote in your C# project. Now go ahead and double click on the ‘add: int32(int32,int32).

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The code represented here is already alot more usefull to your machine than the code you wrote in C#. Let’s just examine some of this code. The add function contains an int32 a and int32 b. These are the 2 arguments your add function will call in.

IL_0001: ldarg.1 -> here ‘a’ is stored.
IL_0002: ldarg2 -> a and b are stored.
IL_0003: add -> this is where ldarg2 gets calculated (which is a + b)
IL_0004: stloc.0 -> Places a value from the stack into the local variable 0
IL_0005: br.s -> means go to IL_0007 in this case.
IL_0007: Loads the local variable 0 onto the stack.
IL_0008: ret -> returns the result of your calculation (a+b)

And that’s the IL code for the add function in C#. Now if you paid attention you should have noticed that IL stands for intermediate language, so this indicated that compiling code into IL is not the last step code has to go trough before being executed by your machine, but we’ll dig deeper into that in a later post.