CLR & Runtime
The C# compiler does not produce machine code. It produces IL (Intermediate Language) and metadata packaged into an assembly, and the CLR (Common Language Runtime) — the managed execution environment — JIT-compiles that IL for the actual CPU, lays objects out on the managed heap, collects garbage, enforces type safety, and exposes the metadata that reflection reads. Understanding the CLR means understanding what happens between "I wrote C#" and "the CPU runs instructions".
The recurring theme: the runtime, not you, owns object lifetime and code translation. Objects are freed by a non-deterministic GC; native code appears lazily through the JIT; identity and versioning live in the assembly manifest and strong name; and the metadata is rich enough that reflection, LINQ providers, and serializers all work over types unknown at compile time. The map below runs from the execution model up to the services built on metadata.
Topic map
- Managed code — what "managed" means: execution under the CLR with garbage collection, type safety, and JIT, instead of running directly on the OS.
- IL and JIT — the C# → IL → native path: the compiler emits bytecode, the JIT translates it per method on first call and caches it.
- Assemblies —
.dll/.exeas the unit of deployment: manifest, type metadata, embedded IL, and resources. - Strong naming — a globally unique identity via public key and signature; GAC installation without version collisions.
- Garbage collection — generations 0/1/2, reachability tracing, and mark-and-compact instead of manual freeing.
- Reflection — inspecting and invoking type metadata at runtime via
System.Reflection. - LINQ — unified query operators over
IEnumerable/IQueryablewith deferred execution. - Serialization — an object graph ↔ JSON/XML/binary for storage and transport across process boundaries.
Common traps
| Mistake | Consequence |
|---|---|
| Assuming C# compiles straight to native code | The IL stage is skipped; native code is actually born from the JIT on a method's first call |
| Expecting deterministic release on scope exit | The GC collects non-deterministically; resources need IDisposable/using, not a finalizer |
| Believing .NET uses reference counting | Cycles would "leak"; the GC traces reachability from roots and collects cycles instead |
Calling GC.Collect() by hand as an "optimization" | It usually hurts throughput: it breaks the generational heuristic and forces full collections |
| Confusing an assembly with a namespace | A namespace is just names; an assembly is a physical deployment unit with a manifest and version |
| Thinking a strong name encrypts the assembly | It signs for identity and integrity, not confidentiality; the IL stays readable |
| Thinking LINQ executes eagerly | Most queries are deferred; re-enumerating re-runs the pipeline over a possibly-changed source |
| Calling reflection in a hot path | Metadata access is slower than a direct call; cache MemberInfo or compile a delegate |
| Treating an incoming serialized payload as safe | Deserializing untrusted data (especially binary) can fail or execute arbitrary code |
Interview relevance
The CLR comes up on middle/senior C# interviews, and the check is your execution model — who owns memory, where native code comes from, where type identity lives — not fact recall. A candidate who explains GC.Collect() as "it breaks the generational heuristic" immediately stands out from "well, it clears memory".
Typical checks:
- Managed code and the C# → IL → native path through the JIT (and why a method's first call is "cold").
- How the GC decides an object is unreachable; generations, promotion, and why reference counting is not involved.
- What an assembly carries (manifest, metadata, IL) and how a strong-name identity differs from a file name.
- That reflection can not only read metadata but also instantiate types and invoke members — at a real cost.
- LINQ's deferred execution and the
IEnumerablevsIQueryabledistinction.
Common wrong answer: "You should call GC.Collect() to free memory." That opens the discussion that the GC runs on its own schedule, that a manual call usually hurts, and that timely resource release is what IDisposable/using is for — not a finalizer.