.NET Ecosystem Map — How Language, Runtime, and BCL Fit Together
- .NET Ecosystem Map — How Language, Runtime, and BCL Fit Together
- .NET History — From Framework to One .NET
- CLR · Mono · IL2CPP · NativeAOT — Comparing the Runtime Branches
- C# is a language, .NET is a platform, CLR is a runtime. They sound like the same thing, but they live on different layers.
- C# code is compiled into an intermediate language called IL. The runtime then translates that IL into native code via JIT or AOT.
- Unity runs on its own runtime — Mono or IL2CPP — and uses a subset of .NET. The accurate statement is not "Unity is not .NET" but "Unity uses a specific implementation of .NET."
Introduction: The ambiguity behind “.NET”
In developer documentation, resumes, and blog posts, the word “.NET” is used with a surprisingly wide range of meanings.
- “I build backends with .NET”
- “This project targets .NET Framework 4.8 only”
- “We migrated to .NET 8”
- “Unity is not .NET” / “Unity is basically .NET”
The same word floats ambiguously across language, runtime, standard library, and application framework. Without sorting this out once, nearly every episode of this C# series ends up wobbling on that ambiguity.
This post has exactly one goal: being able to tell which layer “.NET” is pointing to in any given context.
History (part 2) and runtime variants (part 3) come later. Today, the goal is to draw the map.
Part 1. Six Layers
.NET is not a single technology — it is a tower of six stacked layers. Separating them one by one makes it possible to say which layer “.NET” is referring to at any moment.
Each layer depends only on the layer directly beneath it. C# code passes through Roslyn to become IL; IL is interpreted by the Runtime; the Runtime provides the BCL as its standard library; Application Frameworks run on top of all that.
The word “.NET” points to whichever layer the context calls for. Every discussion from here on uses this map as its reference.
Part 2. C# and .NET
The most common source of confusion comes first.
C# is a language
C# is a programming language standardized as ECMA-334 and ISO/IEC 23270. A specification document defines its syntax and type system, and the compiler (Roslyn) implements that document. C# is a concept at the same level as Java.
.NET is a platform
.NET is the name of the ecosystem that encompasses language, runtime, BCL, SDK, and application frameworks together. It refers collectively to layers 2 through 6 in the diagram above.
Two different things
All four of the following are distinct statements:
| Statement | What it refers to |
|---|---|
| “I wrote it in C#” | Layer 1 (language) — compiler and runtime unspecified |
| “I wrote it targeting .NET 8” | All of layers 2–6 — a specific version |
| “It only runs on .NET Framework 4.8” | The Windows-only .NET implementation |
| “Unity’s C#” | C# language + Unity’s specific runtime (Mono or IL2CPP) |
To answer “aren’t C# and .NET the same thing?”: C# is one layer (language) within .NET.
Part 3. IL — Why the Middle Layer Exists
The layer worth paying the most attention to in the diagram is layer 3, IL. Once this clicks, every later runtime discussion becomes easier.
The compile and execution pipeline
Why insert a middle layer (IL)?
In C, the compiler translates source code directly into native code. A .c file becomes a Windows x64 .exe or a Linux ARM64 binary. The result of a single compilation is locked to a specific OS + CPU combination.
.NET splits this structure in two. C# source is first translated only into IL (Intermediate Language), a hardware-independent and OS-independent intermediate bytecode. The actual native translation happens at execution time (JIT) or just before deployment (AOT) — after the target machine is known.
This design delivers three benefits.
① Platform independence. The same .dll runs on Windows, Linux, and macOS. The runtime handles translation into the machine’s native instructions.
② Language independence. Because any language only needs to produce IL, F# and VB.NET can share the same runtime and BCL as C#. The lower layers stay unchanged regardless of which language is used.
③ Runtime optimization. JIT can optimize using real hardware characteristics and runtime statistics. The trade-off is translating later than a static compiler, in exchange for access to more information.
JIT and AOT — Same IL, Different Translation Timing
There are two points in time at which IL can be translated to native code.
- JIT (Just-In-Time): translates on the target machine, at the moment of execution. Translation cost is included in execution time, but hardware information is known precisely. Standard desktop .NET and Mono use this approach.
- AOT (Ahead-Of-Time): translates to native code before deployment. No JIT is needed at execution time. On platforms like iOS that do not allow JIT, this is the only option. IL2CPP and NativeAOT use this approach.
This boundary explains the Unity Mono vs IL2CPP choice, mobile and console build pitfalls, and why Reflection.Emit breaks under IL2CPP. The details are covered in part 3.
Part 4. Runtime and BCL — The Two Pillars of Execution
Layers 1 through 3 were about “what is produced from source code.” Layers 4 and 5 are about “how what was produced actually runs.”
Runtime — the engine that actually runs IL
The Runtime is the engine that reads IL, translates it to native code, and executes it. It handles memory management (GC), exception handling, the type system, thread management, and security. The Microsoft glossary defines it as:
A CLR handles memory allocation and management. A CLR is also a virtual machine that not only executes apps but also generates and compiles code on-the-fly using a JIT compiler. (Microsoft Learn — .NET glossary)
The key point is that there is not just one runtime.
- CLR — the runtime for .NET Framework. Windows only.
- CoreCLR — the runtime for .NET 5+ (formerly .NET Core). Cross-platform.
- Mono — an open-source cross-platform implementation. Unity’s default runtime.
- IL2CPP — an AOT runtime built by Unity. Converts IL to C++ and then compiles natively.
- NativeAOT — the official .NET AOT deployment mode.
Even with the same C# code and the same IL, which runtime it runs on determines performance characteristics, memory behavior, and which features are available.
BCL — the standard library every runtime provides by default
The BCL (Base Class Library) is the standard library under the System.* namespace. Console.WriteLine, List<T>, Dictionary<K,V>, File.ReadAllText, Task, CancellationToken — nearly everything used without a second thought is part of the BCL.
The Microsoft glossary defines it as:
A set of libraries that comprise the
System.*(and to a limited extentMicrosoft.*) namespaces. The BCL is a general purpose, lower-level framework that higher-level application frameworks, such as ASP.NET Core, build on. (Microsoft Learn — .NET glossary)
The BCL’s position in one sentence: “The runtime is the engine that runs IL; the BCL is the standard library shipped with that engine.” Without the library, even a single Console.WriteLine("hi") cannot run.
Relationship between the three terms
| Term | Layer | Example | Role |
|---|---|---|---|
| Assembly | Layer 3 (IL bundle) | MyApp.dll, System.Collections.dll | File unit containing IL |
| Runtime | Layer 4 | CLR / CoreCLR / Mono / IL2CPP | Engine that actually runs IL |
| BCL | Layer 5 | System.* namespace | Standard library shipped with the runtime |
When someone says “install the .NET Runtime,” they typically mean installing the layer 4 (engine) + layer 5 (BCL) bundle. These two are almost always deployed together.
Part 5. SDK vs Runtime — Developer vs End User
One question always comes up when talking about deployment: “Should I install the SDK or the Runtime?”
The answer depends on the role.
- SDK (Software Development Kit): needed by anyone building an app. Includes the compiler, CLI tools (
dotnet), the runtime, BCL, and project templates. - Runtime: needed by anyone who only needs to run an app. Contains the engine (layer 4) and BCL (layer 5) only. No compiler.
The commands to check what is installed:
1
2
3
4
5
6
7
8
$ dotnet --list-sdks
8.0.404 [/usr/local/share/dotnet/sdk]
10.0.100 [/usr/local/share/dotnet/sdk]
$ dotnet --list-runtimes
Microsoft.NETCore.App 8.0.11 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.AspNetCore.App 8.0.11 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Developer machines typically have the SDK installed, which includes the runtime. Servers and end-user machines only need the runtime.
That said, a self-contained deployment bundles the runtime inside the app itself, so the target machine needs nothing installed. Building with NativeAOT goes further and produces a single native binary. The choices available at layer 4 extend all the way here.
Part 6. Where Unity Fits on the Map
Most readers of this blog are game programmers. The map is not complete without marking Unity’s position on it.
All three paths share the same C# and the same IL. The only layers that differ are layer 4 (runtime) and layer 6 (application framework). The reason Unity appears to be “not .NET” is simply that it uses a different runtime.
Unity’s two runtimes
Mono (JIT) — the default for the Unity Editor and desktop builds. Builds are fast and well-suited for iterative development. Mono is explicitly listed in the Microsoft glossary as “one of the implementations of .NET.” Microsoft Learn — .NET implementations
IL2CPP (AOT) — the default and mandatory choice for iOS, WebGL, and console targets. It converts IL into C++ source, then uses each platform’s native toolchain (Xcode, Emscripten, console SDK) to produce a native binary. iOS does not allow JIT at the OS level, so Mono is simply not an option there. Unity Manual — IL2CPP overview
What game programmers should take from this map
Three practical conclusions from this map.
① The root cause of Reflection.Emit breaking under IL2CPP is that IL2CPP has no JIT to generate new IL at runtime. A change at layer 4 propagates up to the behavior of APIs in the layers above.
② Unity’s “C#” is bound to the runtime and BCL combination Unity has chosen. APIs available in server-side .NET may be absent in Unity, and vice versa. The API Compatibility Level setting in .csproj is exactly the switch that defines the scope of layer 5.
③ The answer to “does Unity belong to the .NET ecosystem?” is clear. It uses layers 1–3 (C#, Roslyn, IL) unchanged, and only at layer 4 (runtime) does it use a Unity-specific implementation. It is simply a different implementation within the same ecosystem.
Summary
Three lines to close the map built today.
- C# is the language at layer 1; .NET is the platform spanning layers 2–6. They are not at the same layer.
- The IL middle layer is the core trade-off that enables language independence, platform independence, and runtime optimization.
- Multiple runtimes (CLR, CoreCLR, Mono, IL2CPP, NativeAOT) coexist, and which runtime the code runs on determines the performance characteristics, constraints, and available APIs for the same code.
This map is the coordinate system every subsequent post in this series will reference. For any post going forward, start by asking: “which layer is this discussion about?”
Next in the Series
- Part 2. .NET History — From Framework to One .NET — The lineage from .NET Framework 1.0 in 2002 through the .NET 5 unification in 2020 and up to .NET 10 today. Why “Core” was dropped from the name and why version 4 was skipped are both explained here.
- Part 3. CLR, Mono, IL2CPP, NativeAOT — Comparing the Runtime Variants — A comparison of the five layer-4 implementations across JIT/AOT, memory, reflection, and generics. This will be the most practically useful episode for game programmers.
References
- Microsoft Learn — .NET glossary · Official terminology reference
- Microsoft Learn — .NET implementations · Implementation overview
- Microsoft Learn — Common Language Runtime (CLR) overview · CLR overview
- Unity Manual — IL2CPP overview · Unity IL2CPP documentation
- Unity Manual — Scripting backends introduction · Mono vs IL2CPP background
