DEV Community

Gokul
Gokul

Posted on

How .NET Really Works: From Build to CLR Execution Explained Simply

I used to wonder why we use a “slow and bloated” framework when languages like C produce tiny binaries and run so efficiently. I never really knew what happened under the hood of every .NET app I created, and each new release hides even more details behind abstraction.

I promised myself that, to love my job, I must first appreciate the tech I work with. This article is my attempt to understand how .NET and its runtime work. Hope you enjoy it! 😊

We all know C# is a compiled language so there is are 2 phases in it Build Phase and Run Phase

Let’s start with what happens during the build phase.

Calm before the storm - the so-called Build phase 🤣

Internally, MSBuild is invoked when we enter dotnet build in CMD:

  1. SDK selection MSBuild checks the SDK specified in myapp.csproj, for example:
   <Project Sdk="Microsoft.NET.Sdk.Web" />
   <!-- or -->
   <Project Sdk="Microsoft.NET.Sdk" />
Enter fullscreen mode Exit fullscreen mode

SDKs live under:

   C:\Program Files\dotnet\sdk\<version>\Sdks
Enter fullscreen mode Exit fullscreen mode
  1. Load .props files

    These set environment variables, global properties, and defaults (output path, configuration, etc.).

  2. Gathers all project items which includes

    • *.cs source files
    • Referenced assemblies (e.g., System.Net.Http.dll) these are the using libraries which use in our c# code, each using statement corresponds to a .dll file which is placed in C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.36
    • NuGet packages (e.g., StackExchange.Redis)
  3. Load .targets files

    These define what actual build steps to run. these can include predefined sequece of steps like

    1. Restore
    2. PrepareResources
    3. Compile
    4. GenerateAssemblyInfo
    5. CopyFilesToOutputDirectory
    6. Build
  4. During above sequence we have Compile step

    During compile step MSBUILD will use roslyn compiler to compile .cs file into IL ( .dll files)

    • compiler will enforce CLS rules
    • compiler also maps all language specific types to CTS-defined types ( e.g., int -> System.Int32 )
    • Both CLS ( Common language specification ) and CTS ( Common Type System ) plays vital role in produced IL( Intermediate Language ) code.
  5. Output

    MSBuild drops the generated .dll, optional .exe, *.pdb, *.deps.json, etc. into:

.\bin\Debug\net8.0\
Enter fullscreen mode Exit fullscreen mode

The Actual strom - The Run Phase 🍃

dotnet run
Enter fullscreen mode Exit fullscreen mode

Does two things:

  1. dotnet build ( will be skipped if already done and no changes are detected )
  2. dotnet myapp.dll ( actual execution of your application starts here )

Note:

So far, we have compiled our code into .dll and .exe files along with other dependencies.

  • These files are also called IL (Intermediate Language) code.
  • They cannot be directly executed on Windows, Linux, or any OS.

Why?

Because they are not machine code.

You may ask:

What is the purpose of this extra step? Why not directly convert the source code into machine code?

There are several reasons why this was implemented:

  • Cross-platform: IL runs anywhere the CLR exists.
  • Runtime optimisation: JIT tailors code to the actual CPU.
  • Security & verification: CLR checks IL for type safety and memory violations.
  • Language interoperability: C#, F#, VB.NET, etc., all compile to the same IL.

Enable verbose host tracing

Before we proceed further its better if we enable below flag for detailed CMD traces.

set COREHOST_TRACE=1
Enter fullscreen mode Exit fullscreen mode

Execution starts when dotnet myapp.dll is called :

As part of execution below command is executed internally.

dotnet exec --additionalprobingpath C:\Users\YourName\.nuget\packages C:\Users\YourName\Desktop\myapp\bin\Debug\net8.0\myapp.dll
Enter fullscreen mode Exit fullscreen mode

so what it does ?

It kicks of an Execution Sequence called CoreHost chain :

What happens when we do dotnet run

  1. dotnet.exe ( located in "C:\Program Files\dotnet\dotnet.exe" ) is called.
    • This acts as the entry point of CoreHost chain.
    • From here directly calls hostfxr.dll file
  2. Hostfxr.dll is called!
    • resolved runtime sdk
    • Uses --additionalprobingpath to locate NuGet packages
    • Chooses appropriate framework
  3. hostpolicy.dll is called :
    • loads dependecy based on .deps.json file (found along with myapp.dll bost build ).
    • Resolves and loads all runtime + app assemblies. basically loads below things
      • our app dll (myapp.dll)
      • Reference library if any ( Newtonsoft.Json.dll )
      • .Net runtime assemblies like ( System.Console.dll )
      • Other Dependency files mentioned in *.deps.json file.
    • Finally once all these are done, coreclr.dll is called.
  4. coreclr.dll :
    • The fricking start of the clr itself.

lets talk about what clr is and why it is important.

As discussed before, the CLR (Common Language Runtime) acts as a runtime platform or container environment capable of processing and executing IL code. It also provides several essential runtime services:

  1. Memory management via Garbage Collection

  2. Conversion of IL to machine code using the JIT compiler, which optimizes based on the runtime machine

  3. Type safety verification through the CTS (Common Type System)

Once all these steps are completed the app will be up and running 🥳.

Simple portrayal for execution

Top comments (0)