C# is a language, and a language is only grammar — it cannot allocate memory, open a file, or turn itself into machine code. .NET is everything that must exist for the grammar to actually run.
C# is a set of rules for writing text. By itself it does nothing. It cannot reserve a byte of memory, cannot read a file, cannot send a packet, and — the point order 1 ended on — cannot turn itself into the machine code a CPU consumes. Every one of those abilities has to come from somewhere outside the language.
That "somewhere" is .NET. And the machine-code translation, though it is where order 1 left off, is the smallest part of what is missing.
Order 1 established that native code is pinned to a CPU-plus-OS pair. Spell that matrix out and the problem becomes obvious:
Windows x64 Linux x64 macOS Intel
Windows ARM64 Linux ARM64 macOS Apple Silicon
... and every future architecture
If C# compiled straight to native code, shipping one program would mean building and maintaining a separate native output for every cell in that grid — and every new chip family would break everything already shipped. That does not scale, and it never gets easier.
The escape hatch is the intermediate language from order 1. C# compiles once to IL, which is CPU-neutral and belongs to no cell in the grid. The grid problem does not disappear — it moves from the language to the runtime. And the runtime is the one piece Microsoft ships for each platform, so it is solved once, centrally, instead of by every program.
C# source
│
▼
Roslyn compiler (happens once)
│
▼
IL (CPU-neutral)
│
┌────────┼────────┐
▼ ▼ ▼
Windows Linux macOS
runtime runtime runtime (each finishes IL → native)
│ │ │
▼ ▼ ▼
native native native
One MyApp.dll full of IL runs on all three operating systems because each platform ships a .NET runtime that performs the final IL-to-native step for its own CPU. This is what "build once, run everywhere" actually means — not magic in the DLL, but a runtime already present on the far end.
Suppose portability were the only problem. A bare language would still leave every program to reinvent the same hard, universal machinery from scratch. .NET supplies it once, for every program and every .NET language:
None of these are language features. They belong to the platform. C# borrows all of them from .NET, which is why the language definition can stay small.
.NET is a managed platform: while a program runs, the runtime actively manages its memory and execution, rather than leaving the program to manage them itself. The trade is explicit — surrender manual control in exchange for safety, portability, and speed of development.
This is the opposite of an unmanaged language like C or C++, where the program takes direct control of memory and pays for it with direct responsibility. Managed execution is the reason later topics keep returning to three components — the CLR, the JIT, and the GC. They are the machinery that does the managing.
The confusion behind "if C# already exists, why is .NET needed?" is a category error. The two answer different questions:
Grammar with no world to run in accomplishes nothing; a world with no grammar has nothing to say. C# and .NET are two halves of one usable whole.
Which raises the next confusion. If .NET is this whole bundle — runtime, libraries, type system, tools — then what is it, exactly? People call it a language, a framework, a runtime, an SDK, and a platform, sometimes in the same sentence. Order 3 settles which of those is true.
"Run everywhere" has a quiet asterisk: it means "run everywhere a runtime has already been ported." IL does not conjure execution on a platform that has no .NET runtime built for it — the portability of the language is only ever as wide as the availability of the runtime. The genius is that porting the runtime once unlocks every existing .NET program at once, but that first port is still real work someone has to do.