This episode opens the .NET hood: compilation of C# to IL and execution by the CLR, the garbage collector, JIT, and solution and project structure. You will also understand the Generic Host and the modern application configuration flow.

In episode 1 you understood the history and the reasons for choosing .NET. Now we go one level deeper: how .NET applications work under the hood. Episode 2 dissects the core architecture — compilation to IL, execution by the CLR, the garbage collector, JIT, all the way to project structure and the Generic Host.
Understanding this architecture matters because many behaviors you'll see later — why an application is slow at first and then fast, why there's a .csproj file, why configuration is read from multiple sources — all have their roots in the design of the .NET runtime. You don't need to memorize every detail, but you should know the main flow.
The C# language doesn't become machine code directly. The compiler turns C# into IL (Intermediate Language) in the form of an assembly. When the application runs, the CLR (Common Language Runtime) loads the assembly, validates types, and then translates the IL into native code via the JIT.
Because IL is language-agnostic, C#, F#, and VB.NET can all run on the same runtime. This translation happens per method the first time it is called — that's why the first method is usually the slowest. Its further optimization is called tiered compilation.
The garbage collector (GC) manages memory automatically: it allocates objects on the managed heap, tracks references, and frees objects that are no longer used. Developers don't need to call free or delete — this behavior is what prevents memory leaks in most .NET applications.
Besides JIT (Just-In-Time), .NET supports AOT (Ahead-Of-Time) through NativeAOT: the application is fully compiled to native code at publish time. The result is very fast startup and small binaries, suitable for serverless or edge. The balance between the two will be discussed in episodes 15 and 22.
One compilation unit is a project, described by an SDK-style .csproj file. Related projects are organized in a solution (.sln). Here's how to create both from the CLI:
mkdir MyApp
cd MyApp
dotnet new sln -n MyApp
dotnet new classlib -n MyApp.Core
dotnet new console -n MyApp.Cli
dotnet sln add MyApp.Core MyApp.Cli
dotnet builddotnet new sln -n MyApp creates a solution file, and dotnet sln add registers projects in it. Every project has a TargetFramework — for example net8.0 — which determines the available APIs. SDK-style projects are very concise: the entire configuration lives in one XML file.
NuGet is .NET's package registry. Dependencies are written as <PackageReference> in .csproj, and resolution is handled by NuGet restore. See all packages of a project with:
dotnet list packagedotnet list package shows the name and version of every dependency, including transitive ones. Restore happens automatically at build time, so lock files (similar to package-lock.json) keep reproducibility intact.
Since .NET Core, modern applications are built on top of the Generic Host. The host is a container that manages dependency injection, configuration, logging, and service lifetimes. You can even write a web API or a worker from an empty template:
dotnet new worker -n MyWorkerdotnet new worker -n MyWorker creates a project containing Program.cs with Host.CreateApplicationBuilder. The host is responsible for clean startup and shutdown — from background services to web servers, everything lives inside the same host.
Configuration is read from many providers in order: appsettings.json, appsettings.{Environment}.json, environment variables, and finally the command line. The later provider overrides the earlier one. The environment variables used are DOTNET_ENVIRONMENT or ASPNETCORE_ENVIRONMENT.
Info
Don't build a .NET application without the Generic Host unless the minimum requirements demand it. The host unifies DI, configuration, and logging so your code stays structured and easy to test.
An assembly is the deployable unit containing IL and type metadata. When the application runs, assembly loading loads assemblies into the process — this is the mechanism that allows plugins and modules to be loaded dynamically. See the currently loaded assemblies from within your code:
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine(asm.GetName().Name);
}AppDomain.CurrentDomain.GetAssemblies() returns all assemblies already loaded by the process. This detail matters when diagnosing library version conflicts or deciding on a plugin strategy.
In general, let the runtime manage loading — intervene manually only when truly necessary, for example when handling version conflicts between libraries.
A summary of your code's journey from source to a running application:
Understanding this flow helps you read error messages, fix slow startups, and diagnose memory issues in episode 15. Starting with episode 3, every example will run on top of this lifecycle.
Key takeaways:
In the next episode 3 we will discuss starting your first .NET application — hands-on practice creating a project with dotnet new, understanding the Program.cs and csproj folder structure, then running, building, and publishing your first console application.