This episode breaks down how C# works behind the scenes: compilation to Intermediate Language, execution by the CLR with JIT and the garbage collector, the concept of assemblies, metadata, and manifest, and the .NET solution and project structure along with its build flow.

In episode 1 you understood the history of C# and why this language is needed. Now we go one level deeper: how C# code is actually executed on a machine. This isn't purely academic material — understanding the runtime architecture will help you when debugging, optimizing performance, and choosing the right project structure.
Think about your workflow today: you write code, press dotnet run, and the result appears. Between those two moments, a lot happens that has been hidden until now: source code is compiled, loaded as an assembly, recompiled by the JIT, and then executed with help from the garbage collector.
Episode 2 dissects that flow step by step: compilation to IL, execution by the CLR, the concept of assemblies and metadata, the .NET project structure, and the build and execution flow with the dotnet CLI.
Don't worry if this part feels abstract. Some concepts will become clearer when you see a real build output at the end of the episode. What matters now is mapping out a mental picture: what happens from source code to a running application.
To make it easier, think of this process like a production line: source code is the raw material, Roslyn is the processing machine that prints the IL, the CLR is the supervisor coordinating the workers, and the garbage collector is the cleaning crew that keeps the work area tidy. When it all runs, you only see the end result: a smoothly running application. We'll reuse this analogy in the following episodes.
When you run dotnet build, the C# compiler named Roslyn translates source code into Intermediate Language (IL) — byte code that is not specific to any single CPU. This IL is what allows C# to run on Windows, Linux, and macOS without rewriting the code. The compilation process also produces metadata describing all types, methods, and fields in your code.
Program.cs --Roslyn--> IL + metadata --CLR--> JIT --> kode mesinThe compiled result, an assembly, is executed by the Common Language Runtime (CLR), known as CoreCLR in its modern implementation. The CLR is a managed runtime: it manages code execution, enforces type safety at runtime, and provides services such as exception handling and code access security.
At execution time, called methods are translated from IL to native machine code by the Just-In-Time compiler (JIT). The JIT only compiles methods that are actually called, and in episode 15 we'll look at tiered compilation, a feature that makes the JIT progressively faster as the program runs.
The advantage of this approach is pure portability: the same assembly can be executed on different CPU architectures. The IL is recompiled into native instructions matching the processor where the application runs, without changing the source code at all.
The CLR also runs a generational garbage collector (GC) that divides the heap into generations 0, 1, and 2. Objects that survive long enough get promoted to the next generation, so the GC can focus its work on young objects, which are most often no longer needed. This is why C# developers don't need to free memory manually.
While the application runs, the GC works in the background: allocating memory for new objects, tracking references, and reclaiming memory from objects that are no longer referenced. As a result, common errors like double free and use-after-free, common in C and C++, are practically impossible in C#.
The build output is a .dll file called an assembly. An assembly stores three things: IL, type metadata, and a manifest that contains the assembly identity, version, and dependency list. In modern .NET, the SDK version is encoded as an assembly informational version, and you can inspect it with tooling:
dotnet build -c Release
ls bin/Release/net8.0/The dotnet build -c Release command produces the assembly in the bin/Release/net8.0 folder. You'll see a *.dll file containing the application code, a *.deps.json file listing the dependencies, and a *.runtimeconfig.json file containing runtime settings. All three are required when publishing. This three-file structure is why a .NET project can run directly with dotnet run without extra configuration.
From an execution standpoint, this assembly is what the CLR reads every time you run the application. dotnet run is actually a build-then-execute sequence: the project is compiled first, the resulting assembly is run, and if there are no code changes, the already-built output is reused. This explains why the first run feels slower than subsequent ones.
Large .NET projects are organized with a solution (a .sln file) that holds many projects. A common convention: a src folder for application code and a tests folder for test code. Create the standard structure:
dotnet new sln -n Toko
dotnet new console -n Toko.App -o src/Toko.App
dotnet new xunit -n Toko.Tests -o tests/Toko.Tests
dotnet sln Toko.sln add src/Toko.App tests/Toko.TestsThe dotnet sln Toko.sln add src/Toko.App command registers the project in the solution. We'll use this structure starting in episode 4, and we'll cover the testing details in episode 10.
Dependencies are added through NuGet, .NET's official package manager. Modern projects use the SDK-style format: a short .csproj file that automatically includes every .cs file in the folder. Add a dependency with:
dotnet add src/Toko.App package Newtonsoft.JsonThe dotnet add ... package Newtonsoft.Json command downloads the package and records it in the project file. All transitive dependencies are resolved automatically at build time. NuGet is the source of hundreds of thousands of ready-to-use libraries — this is the ecosystem that makes C# so productive.
The entire project lifecycle runs through the dotnet CLI. The most frequently used commands:
dotnet build: compiles the project and produces the assembly.dotnet run: builds and then runs the application directly.dotnet test: builds and runs all unit tests.dotnet publish: produces deploy-ready output with complete dependencies.dotnet build -c Release
dotnet run --project src/Toko.App
dotnet publish src/Toko.App -c ReleaseThe dotnet publish -c Release command produces an output folder containing the application and all its dependencies — this is what we'll package into Docker in episode 19.
Key takeaways:
In the next episode 3 we'll start writing real code: basic syntax and program structure — variable declarations with var and const, basic data types, operators, control flow, and a complete first console program. Prepare your editor!