C# is one of the languages belonging to .NET family. .NET languages have common creation and execution of their assemblies within CLR (Common Language Runtime).
Compiled languages are known for their straight compilation of the source code right into the processor instructions which are executed later at a target device runtime. On the other hand, .NET languages do this process a little bit differently. However, one cannot say that the .NET languages are either compiled or interpreted, because .NET languages are a mixture of these two mechanics together.
Compilation
Right after you write your source code and run the compilation in Visual Studio or any other IDE (alternatively any other compilator), then your code gets translated (compiled) into low-level programming language known as MSIL (Microsoft Interpreted Language, next time just CIL (Common Interpreted Language)), and an assembly gets created in either .exe or .dll format.
This CIL language works with low-level instructions like the Assembler ones. The following CIL code example (main method) shows the emitted code from simple Hello World program that will be shown later.
- .method private hidebysig static void Main(string[] args) cil managed
- {
- .entrypoint
-
- .maxstack 8
- IL_0000: nop
- IL_0001: ldstr "Hello World!"
- IL_0006: call void [mscorlib]System.Console::WriteLine(string)
- IL_000b: nop
- IL_000c: call string [mscorlib]System.Console::ReadLine()
- IL_0011: pop
- IL_0012: ret
- }
And, here is the Hello World program written in C#.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace HelloWorld
- {
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("Hello World!");
- Console.ReadLine();
- }
- }
- }
All this CIL code is generated automatically during the compilation. This short example is just for illustration of what the raw CIL code looks like. However, more detailed analysis of the CIL would be far beyond the scope of this short article.
Detailed understanding or writing of the CIL code is unnecessary for most of the applications but there are a few situations where a deeper understanding of CIL code is important. These situations are for example creation of the dynamic assemblies. This concept of dynamic assemblies works with System.Reflection.Emit namespace where the programmer can emit the CIL code during the runtime and in this way create the dynamic assembly. Topic of the dynamic assemblies is very advanced and probably you’ll never deal with it.
If you want to explore more of the emitted CIL code from your source code, you can get in touch with tool ildasm.exe (Interpreted Language Disassembler). You can run this tool through Visual Studio Command Prompt where you just insert ildasm command confirmed by enter and you’ll get the ildasm client opened.
There in the client through toolbar select File/Open option and choose your CIL assembly generated from your compiler (.exe or .dll). Disassembler will then disassemble the whole assembly and will show you what your assembly contains. Next, you’ll not see just the appropriate CIL code but there you can find other data such as Manifest or the Metadata of the opened assembly.
There is also the possibility to edit the generated code on the level of CIL instructions and after the editing you can recompile it using the ilasm.exe (Interpreted Language Assembler).
This was the process of the compilation of any .NET language and now follows the review of the execution of the compiled assembly.
Execution/CLR
One of CLR's (Common Language Runtime) responsibilities is the execution of the .NET assemblies. CLR is one of the modules within the .NET framework and all the .NET assemblies are executed through CLR.
I am going to declare the next term by the following image – JIT (Just In Time) compiler.
As the given infographics suggest, the JIT compiler is supposed to translate the CIL code instructions of the given assembly into the native code of the target device.
This compilation is executed at the launch of the assembly. There is a little delay at the launch of the assembly because of the JIT compilation. All this compiled native code is saved into the memory and stays there during the whole existence of the corresponding process (more precisely said – during the whole existence of the corresponding Application Domain).
Thanks to this CIL code compilation which is executed on the target device, the code can run theoretically faster than by the classical compilation because the generated native code is processed by the adaptive optimization according to the target device.
In the end, this native code is executed by the target operating system.
To sum up, in this article we’ve explored the background processes of compilation and execution of a .NET application. Next, we’ve begun very briefly a few other topics such as CIL or dynamic assemblies. These two are advanced topics of C#/.NET. If you are interested in any more about this topic, you can let me know. On the other hand, I am planning to create an article about reverse engineering and CIL next time so if you are interested, let me know, too.