When you press Build or Run in Visual Studio or execute "dotnet run", a lot happens behind the scenes. While we write simple C# programs, the .NET runtime performs multiple sophisticated operations to turn that high-level code into instructions your CPU can understand.
In this article, we will discuss how C# code is compiled, loaded, converted, and executed inside the .NET runtime, with all intermediate steps clearly explained.
Several steps take place behind the scenes, and we will understand each of them one by one in sequence.
Step 1: Write a C# Code.
Everything begins with a .cs file that contains C# source code. These files contain human-readable instructions written in the C# programming language. But your computer cannot execute C# directly, so it needs to be converted several times before execution.
using System; namespace MyApplication { class Program { static void Main(string[] args) { Console.WriteLine("Hello World"); } } }
Step 2: The Role of the C# Compiler (Roslyn)
When you write C# code in Program.cs, that code is not directly understood by your CPU. There’s a critical component in between: the C# compiler, also known as Roslyn.
Understanding how the C# compiler (Roslyn) works is key to understanding:
- How your C# source turns into something runnable.
- Why do you see certain errors at compile time?
- How tools like IntelliSense, analyzers, and refactorings work so “smartly” in IDEs.
Let’s walk through this step by step.
What is Roslyn?
Roslyn is the official C# and Visual Basic .NET compiler platform from Microsoft. It converts your C# code into IL (Intermediate Language), also known as CIL (Common Intermediate Language).
Intermediate Language (IL), also known as Common Intermediate Language (CIL) or formerly Microsoft Intermediate Language (MSIL), is a low-level, object-oriented, stack-based bytecode instruction set.
Along with IL, Roslyn also produces metadata that contains type definitions (classes, structs, enums), method signatures, properties, fields, and references to other assemblies.
IL + Metadata is the final output of the Roslyn Compiler.
After compilation of your source code, your project produces assembly:
- .exe file → console app / Windows app
- .dll file → class library
Both contain IL Code + Metadata + Manifest (Assembly name, version, culture, references, entry point). These files are platform-independent and can run on Windows, Linux, Mac, or any OS that supports .NET.
Step 3: Role of CLR (Common Language Runtime)
The Common Language Runtime (CLR) is the heart of the .NET runtime environment.
Just like the Java Virtual Machine (JVM) for Java, the CLR is the execution engine that runs applications written in C#, F#, VB.NET, and other .NET languages.
What does CLR do?
The CLR performs many tasks to ensure that your program runs smoothly. Let's understand each role one by one:
Role 1. Loads and Verifies Assembly.
When you run a .NET application, the CLR reads the manifest present inside the (.exe or .dll) and loads the assembly into your memory. It also verifies the IL code and prepares it for execution. This is the first job of the CLR - loading your compiled IL into the runtime environment.
Role 2: JIT Compilation.
IL cannot run directly on your processor, so the CLR uses the JIT (Just-In-Time) compiler to convert IL into native machine code just before execution. JIT compiles methods only when needed to improve startup time, memory usage, and overall performance.
JIT is one of the biggest strengths of the CLR, giving you portability + performance.
Role 3: Memory Allocation.
The CLR allocates memory on the managed heap and keeps track of the objects. You don't need to manually allocate memory because the CLR handles everything.
Role 4: Garbage Collection.
CLR includes Garbage Collection (GC), which finds unused objects and frees memory automatically to prevent memory leaks. This gives C# extremely efficient memory handling without manual malloc/free.
Garbage Collection works in three generations:
Gen 0: The youngest generation, where all new objects are initially allocated. This is where the majority of short-lived objects reside. Garbage collection runs most frequently in this generation to quickly reclaim memory from objects that are no longer in use.
Gen 1: Objects that have survived a collection in Generation 0 are moved to this generation. These objects are considered to be slightly longer-lived than those in Gen 0. Collection here happens less frequently than in Gen 0.
Gen 2: Objects that survive collection in Generation 1 are moved to this, the oldest generation. This generation holds the long-lived or "permanent" objects that have survived multiple collection cycles. Garbage collection runs least frequently in this generation, as it's assumed these objects will be in use for a long time.
Role 5: Exception Handling
CLR provides a structured and unified exception system. If something goes wrong:
- CLR throws an exception object
- Stops the current execution path
- Jumps to the nearest matching
catch - Ensures cleanup using
finally
Role 6: Thread Management
Role 7: Security Checks
Short Quick Answer: C# code is first compiled by the Roslyn compiler into IL (Intermediate Language) and stored in an assembly (.dll or .exe). When the program runs, the CLR loads this IL and the JIT compiler converts it into native machine code for the current processor. The runtime then executes this native code and handles memory using the Garbage Collector.


No comments
Post a Comment