[{"data":1,"prerenderedAt":115},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Ffundamentals\u002Fclr-runtime":3},{"page":4,"siblings":95,"blog":112},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":90,"topic":91,"topicSlug":92,"updated":93,"__hash__":94},"qa\u002Fdotnet\u002Ffundamentals\u002Fclr-runtime.md","Clr Runtime",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,1,"\u002Fdotnet\u002Ffundamentals\u002Fclr-runtime",[23,28,32,36,40,44,48,52,56,60,64,68,72,77,81],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-clr","easy","What is the CLR and what does it do?","The **Common Language Runtime (CLR)** is the virtual machine at the heart of .NET.\nIt takes compiled **Intermediate Language (IL)** bytecode and executes it on the\nhost OS, providing services that would otherwise be the developer's responsibility.\n\n```csharp\n\u002F\u002F You write C# → the compiler produces IL → the CLR executes IL\n\u002F\u002F dotnet run triggers: csc → assembly.dll (IL) → CLR JIT → native CPU instructions\nConsole.WriteLine(\"Hello from managed code\"); \u002F\u002F CLR handles memory, exceptions, types\n```\n\nThe CLR provides: **JIT compilation** (IL → native code), **garbage collection**\n(automatic memory management), **type safety** enforcement, **exception handling**,\n**thread management**, and **security sandboxing**. Every .NET language (C#, F#,\nVB.NET) compiles to the same IL, so the CLR is truly language-agnostic.\n\n**Rule of thumb:** The CLR is to .NET what the JVM is to Java — an execution engine\nthat abstracts the OS and hardware while enforcing language rules at runtime.\n",{"id":29,"difficulty":14,"q":30,"a":31},"what-is-il","What is Intermediate Language (IL) and how does the CLR execute it?","**Intermediate Language (IL)**, also called **CIL** (Common Intermediate Language)\nor **MSIL**, is the CPU-agnostic bytecode that .NET compilers produce. It is a\nstack-based instruction set — not native machine code, not source code.\n\n```csharp\n\u002F\u002F Source:\nint Add(int a, int b) => a + b;\n\n\u002F\u002F IL (roughly, as shown by ildasm \u002F dnSpy):\n\u002F\u002F ldarg.1   — push 'a' onto evaluation stack\n\u002F\u002F ldarg.2   — push 'b' onto evaluation stack\n\u002F\u002F add       — pop two values, push their sum\n\u002F\u002F ret       — return top of stack\n```\n\nWhen the runtime first calls a method, the **JIT compiler** translates that method's\nIL to native CPU instructions for the current machine. The result is cached for the\nprocess lifetime — the JIT only runs once per method per process. You can inspect\nIL with `dotnet tool install -g dotnet-ildasm` or view it in dnSpy\u002FILSpy.\n\n**Rule of thumb:** IL is the \"assembly language\" of .NET — write once in any CLS\nlanguage, run anywhere the CLR is installed, on any CPU architecture.\n",{"id":33,"difficulty":14,"q":34,"a":35},"jit-vs-aot","What is JIT compilation and how does it differ from AOT?","**JIT (Just-In-Time)** compiles IL to native code at runtime, method by method,\nthe first time each method is called. **AOT (Ahead-Of-Time)** compiles everything\nto native code before the app starts.\n\n```csharp\n\u002F\u002F JIT (default): compilation happens at runtime\n\u002F\u002F First call to Foo() triggers JIT; subsequent calls use cached native code\n\n\u002F\u002F AOT via .NET Native Ahead-of-Time (PublishAot = true in .csproj):\n\u002F\u002F \u003CPublishAot>true\u003C\u002FPublishAot>\n\u002F\u002F dotnet publish -r linux-x64 -c Release\n\u002F\u002F Result: single self-contained native binary, no CLR required at runtime\n```\n\n| | JIT | AOT |\n|---|---|---|\n| Startup | Slower (JIT work on first calls) | Fast (native binary) |\n| Peak perf | High (tiered JIT optimises hot paths) | High but fixed |\n| Deploy size | Needs .NET runtime installed | Larger self-contained binary |\n| Use case | Long-running services | CLI tools, containers, IoT |\n\n.NET 6+ also has **Tiered Compilation**: methods start with a quick \"tier 0\" compile,\nthen hot methods are recompiled with full optimisations at \"tier 1\".\n\n**Rule of thumb:** Use JIT for servers and services (runtime profiling = better\noptimisation); use AOT for CLI tools and latency-sensitive cold starts.\n",{"id":37,"difficulty":14,"q":38,"a":39},"common-type-system","What is the Common Type System (CTS)?","The **Common Type System (CTS)** defines every data type that the CLR understands\nand the rules for declaring, using, and managing those types. It ensures that objects\nwritten in different .NET languages can interact without type-mismatch errors.\n\n```csharp\n\u002F\u002F C# int → CTS System.Int32\n\u002F\u002F VB.NET Integer → CTS System.Int32\n\u002F\u002F F# int → CTS System.Int32\n\u002F\u002F All three are the exact same CLR type — fully interoperable\n\nint x = 42;                   \u002F\u002F C# keyword\nSystem.Int32 y = 42;          \u002F\u002F CTS type name — identical at runtime\nConsole.WriteLine(x.GetType().FullName); \u002F\u002F \"System.Int32\"\n```\n\nThe CTS defines two top-level categories: **value types** (derive from\n`System.ValueType`, stored by value) and **reference types** (derive from\n`System.Object`, stored as references). Every type in every .NET language must\nconform to these rules, which is why a C# `List\u003Cint>` can be consumed by F# code\nwithout any wrapper.\n\n**Rule of thumb:** The CTS is the shared type contract between all .NET languages —\nit is what makes cross-language .NET libraries possible.\n",{"id":41,"difficulty":14,"q":42,"a":43},"cls-common-language-specification","What is the Common Language Specification (CLS) and why does it matter?","The **Common Language Specification (CLS)** is a subset of the CTS that defines the\nminimum set of features a .NET language must support to be interoperable with any\nother CLS-compliant language. It is more restrictive than the full CTS.\n\n```csharp\n\u002F\u002F CLS-non-compliant: unsigned integers are not in the CLS\n\u002F\u002F (VB.NET doesn't have uint, so a public API using uint breaks interop)\npublic uint GetCount() => 42u;  \u002F\u002F not CLS-compliant\n\n\u002F\u002F CLS-compliant fix:\npublic int GetCount() => 42;    \u002F\u002F all CLS languages support int (System.Int32)\n\n\u002F\u002F Mark your assembly as CLS-compliant to get compiler warnings:\n[assembly: CLSCompliant(true)]\n```\n\nFeatures excluded from the CLS include: unsigned integer types in public APIs,\nglobal functions, pointer types, operator overloading in certain forms, and some\nnaming conventions (CLS is case-insensitive for public identifiers). If you are\nwriting a library for broad .NET language consumption, mark it `[CLSCompliant(true)]`\nand the compiler will flag violations.\n\n**Rule of thumb:** Target CLS compliance for public library APIs; internal code can\nuse the full CTS freely.\n",{"id":45,"difficulty":25,"q":46,"a":47},"managed-vs-unmanaged","What is the difference between managed and unmanaged code?","**Managed code** runs under CLR supervision — the runtime handles memory allocation,\ngarbage collection, type safety, and exception propagation. **Unmanaged code** runs\ndirectly on the OS without CLR oversight — the developer is responsible for memory.\n\n```csharp\n\u002F\u002F Managed: CLR allocates and frees memory automatically\nvar list = new List\u003Cstring>();\nlist.Add(\"hello\");\n\u002F\u002F GC will reclaim list when it's no longer reachable — no manual free()\n\n\u002F\u002F Calling unmanaged code via P\u002FInvoke:\n[DllImport(\"kernel32.dll\")]\nstatic extern IntPtr GetConsoleWindow();\n\n\u002F\u002F Unsafe managed code (pointer arithmetic, must mark block):\nunsafe void ProcessBuffer(byte* ptr, int length)\n{\n    for (int i = 0; i \u003C length; i++)\n        ptr[i] = 0; \u002F\u002F direct memory access — no GC protection here\n}\n```\n\nInterop between managed and unmanaged code uses **P\u002FInvoke** (platform invocation)\nor **COM Interop**. The `unsafe` keyword enables pointer operations within managed\ncode but the CLR still bounds-checks when you re-enter safe territory.\n\n**Rule of thumb:** Write managed code by default; drop to P\u002FInvoke or `unsafe` only\nwhen you need OS APIs, native libraries, or zero-allocation hot paths.\n",{"id":49,"difficulty":14,"q":50,"a":51},"what-is-assembly","What is a .NET Assembly and what does it contain?","A **.NET Assembly** is the compiled, deployable unit of .NET code — typically a\n`.dll` or `.exe` file. It is the fundamental unit of versioning, deployment, and\nsecurity in .NET.\n\n```csharp\n\u002F\u002F View assembly info at runtime:\nvar asm = typeof(string).Assembly;\nConsole.WriteLine(asm.FullName);\n\u002F\u002F \"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e\"\n\n\u002F\u002F Load an assembly dynamically:\nvar loaded = Assembly.LoadFrom(\"MyPlugin.dll\");\nvar type = loaded.GetType(\"MyPlugin.EntryPoint\");\n```\n\nAn assembly contains: **IL bytecode** (the compiled code), a **manifest** (name,\nversion, culture, strong-name public key, list of referenced assemblies), **metadata**\n(type descriptions used by reflection), and optionally **resources** (embedded images,\nstrings). A single assembly can span multiple source files but compiles to one binary.\nMulti-file assemblies are rare in .NET Core.\n\n**Rule of thumb:** One project → one assembly. The assembly manifest is what NuGet\nand the runtime use to resolve the right version of a dependency.\n",{"id":53,"difficulty":25,"q":54,"a":55},"dotnet-versions","What is the difference between .NET Framework, .NET Core, and modern .NET (5+)?","**.NET Framework** (2002–2019) is Windows-only, ships with Windows, and is legacy.\n**.NET Core** (2016–2020) was the cross-platform rewrite. **.NET 5+** (2020–present)\nunified both under a single cross-platform runtime, dropping the \"Core\" suffix.\n\n```xml\n\u003C!-- .csproj target framework monikers -->\n\u003CTargetFramework>net48\u003C\u002FTargetFramework>       \u003C!-- .NET Framework 4.8, Windows only -->\n\u003CTargetFramework>netcoreapp3.1\u003C\u002FTargetFramework> \u003C!-- .NET Core 3.1, LTS, EOL 2022 -->\n\u003CTargetFramework>net8.0\u003C\u002FTargetFramework>       \u003C!-- .NET 8 (current LTS), cross-platform -->\n\u003CTargetFramework>net9.0\u003C\u002FTargetFramework>       \u003C!-- .NET 9 (current STS) -->\n```\n\n| | .NET Framework | .NET Core \u002F .NET 5+ |\n|---|---|---|\n| Platform | Windows only | Windows, Linux, macOS |\n| Open source | No | Yes |\n| Side-by-side | No (machine-wide) | Yes (multiple versions per machine) |\n| New features | None (frozen) | Yes (annual releases) |\n\n.NET follows a predictable cadence: even-numbered versions (6, 8, 10) are **LTS**\n(3-year support); odd-numbered (7, 9) are **STS** (18-month support).\n\n**Rule of thumb:** Always target the latest LTS for new projects; migrate .NET\nFramework apps when you need Linux\u002Fcontainer deployment or modern C# features.\n",{"id":57,"difficulty":14,"q":58,"a":59},"garbage-collection-overview","How does the .NET garbage collector work at a high level?","The .NET **Garbage Collector (GC)** is a generational, tracing collector. It\nperiodically identifies objects no longer reachable from any **GC root** (static\nfields, local variables, CPU registers) and reclaims their memory.\n\n```csharp\n\u002F\u002F GC roots keep objects alive:\nstatic List\u003Cstring> _cache = new();   \u002F\u002F static field — GC root, always alive\nvoid ProcessRequest()\n{\n    var data = new byte[1024];         \u002F\u002F local var — GC root while method runs\n    _cache.Add(\"entry\");              \u002F\u002F added to static — stays alive\n    \u002F\u002F 'data' goes out of scope here — eligible for collection next GC cycle\n}\n\n\u002F\u002F Force a collection (rarely appropriate in production):\nGC.Collect(2, GCCollectionMode.Forced, blocking: true);\n```\n\nThe GC uses a **mark-and-compact** algorithm: mark all live objects, then compact\nthe heap by sliding live objects together (eliminating fragmentation). Large objects\n(>85 KB by default) go to the **Large Object Heap (LOH)** which is swept but not\ncompacted by default. The GC can run concurrently with your code in **background GC**\nmode to minimise pause times.\n\n**Rule of thumb:** Don't fight the GC — avoid unnecessary allocations, use `ArrayPool\u003CT>`\nfor large buffers, and implement `IDisposable` only for unmanaged resources.\n",{"id":61,"difficulty":14,"q":62,"a":63},"gc-generations","What are GC generations and why do they matter?","The .NET GC divides the managed heap into three **generations** (Gen 0, Gen 1, Gen 2)\nbased on the observation that most objects die young. Collecting younger generations\nis cheaper because they are smaller.\n\n```csharp\n\u002F\u002F Check which generation an object is in:\nvar obj = new object();\nConsole.WriteLine(GC.GetGeneration(obj)); \u002F\u002F 0 — just allocated\n\nGC.Collect(0); \u002F\u002F collect Gen 0 only (cheapest)\n\u002F\u002F If 'obj' survived, it is now promoted to Gen 1\nConsole.WriteLine(GC.GetGeneration(obj)); \u002F\u002F 1\n\nGC.Collect(1); \u002F\u002F collect Gen 0 + Gen 1\nConsole.WriteLine(GC.GetGeneration(obj)); \u002F\u002F 2 — fully tenured\n```\n\n**Gen 0** — new allocations; collected frequently (milliseconds). **Gen 1** — objects\nthat survived one Gen 0 collection; a buffer zone collected less often. **Gen 2** —\nlong-lived objects (static-like); collected rarely, most expensive. A **Gen 2**\ncollection (full GC) can cause noticeable pauses in latency-sensitive apps.\n\nMemory pressure on Gen 2 is a common production issue — typically caused by excessive\ncaching, long-lived `string` allocations, or `IDisposable` objects not being disposed.\n\n**Rule of thumb:** Short-lived objects (request-scoped) are cheap; long-lived\nobjects (Gen 2) cost GC pressure — minimise how much data you promote to Gen 2.\n",{"id":65,"difficulty":14,"q":66,"a":67},"reflection","What is reflection in .NET and when should you use it?","**Reflection** is the ability of .NET code to inspect and invoke type metadata —\nclass names, methods, properties, attributes — at runtime without knowing them at\ncompile time. It is provided by the `System.Reflection` namespace.\n\n```csharp\nusing System.Reflection;\n\nvar type = typeof(DateTime);\n\n\u002F\u002F List all public instance methods:\nforeach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))\n    Console.WriteLine(method.Name);\n\n\u002F\u002F Invoke a private method dynamically:\nvar mi = type.GetMethod(\"InternalTicksToDateTime\",\n                        BindingFlags.NonPublic | BindingFlags.Static);\n\u002F\u002F mi?.Invoke(null, new object[] { ticks });\n\n\u002F\u002F Read a custom attribute:\nvar attr = typeof(MyService).GetCustomAttribute\u003CObsoleteAttribute>();\nConsole.WriteLine(attr?.Message);\n```\n\nReflection is used by: **dependency injection containers** (scanning for constructors),\n**serialisers** (JSON.NET, System.Text.Json), **ORMs** (EF Core mapping), **test\nframeworks** (discovering `[Test]` methods), and **plugin systems**. The downsides are\nperformance overhead and loss of compile-time type safety.\n\n**Rule of thumb:** Let frameworks use reflection internally; avoid it in your own\nhot paths. Prefer source generators (C# 9+) for compile-time code generation as a\nzero-overhead alternative.\n",{"id":69,"difficulty":14,"q":70,"a":71},"stack-vs-heap","What is the difference between stack and heap allocation in the CLR?","The CLR manages two primary memory regions. The **stack** is a per-thread, LIFO\nstructure for method frames, local variables of value types, and return addresses.\nThe **heap** is a shared, GC-managed area for all reference-type objects.\n\n```csharp\nvoid Example()\n{\n    int x = 10;           \u002F\u002F value type local — lives on the STACK for this frame\n    var s = \"hello\";      \u002F\u002F string is a reference type — object on HEAP, reference on stack\n    var p = new Person(); \u002F\u002F Person object on HEAP, 'p' reference on stack\n}                         \u002F\u002F frame pops: x, s, p references gone; GC eventually frees heap objects\n\nstruct Point { public int X, Y; }   \u002F\u002F value type\nclass Circle { public Point Center; } \u002F\u002F Circle on heap; Center (struct) embedded in it\n```\n\nValue types declared as **fields of a class** live on the heap — inside the object\nthat contains them. \"Value types live on the stack\" is a simplification true only for\nlocal variables and parameters. `Span\u003CT>` and `ref struct` types are designed to be\nstack-only to enable safe, zero-copy buffer operations.\n\n**Rule of thumb:** Stack allocation is free and deterministic; heap allocation\nincurs GC pressure. For hot loops, prefer value types and `Span\u003CT>` to avoid heap\nallocations.\n",{"id":73,"difficulty":74,"q":75,"a":76},"tiered-compilation","hard","What is tiered compilation in .NET and how does it improve performance?","**Tiered compilation** (enabled by default since .NET Core 3.0) allows the JIT to\ncompile methods multiple times at increasing optimisation levels, trading startup\nspeed for peak throughput.\n\n```csharp\n\u002F\u002F You write:\nint Sum(int a, int b) => a + b;\n\n\u002F\u002F Tier 0 (first call): compiled quickly with minimal optimisation\n\u002F\u002F → fast startup, no inlining, no loop unrolling\n\n\u002F\u002F After the method is called enough times (instrumented with call counters):\n\u002F\u002F Tier 1 (hot method): recompiled with full optimisation\n\u002F\u002F → inlined, loop-unrolled, register-allocated aggressively\n\n\u002F\u002F Disable via environment variable (for benchmarking):\n\u002F\u002F DOTNET_TieredCompilation=0\n```\n\nTier 0 uses **instrumentation stubs** to count calls. Once a method crosses a\nthreshold, the runtime queues it for **tier 1** recompilation on a background thread.\nThe old tier 0 code stays active until tier 1 is ready, then the call-site patch\nis swapped atomically.\n\n**ReadyToRun (R2R)** is a related feature that pre-compiles IL to native code at\npublish time, providing faster startup (like AOT) while still allowing tier 1\nrecompilation at runtime for peak performance.\n\n**Rule of thumb:** Tiered compilation is transparent — leave it on. If you're\nmicro-benchmarking (BenchmarkDotNet), disable it or let the benchmark harness warm\nup properly before measuring.\n",{"id":78,"difficulty":14,"q":79,"a":80},"appdomains-and-isolation","What is an AppDomain and how does process isolation work in modern .NET?","An **AppDomain** was the .NET Framework mechanism for isolating multiple logical\napplications within a single process. It provided separate heaps, separate type\nsystems, and remoting between domains. In **.NET Core and .NET 5+**, AppDomains are\nlargely removed — there is only one AppDomain per process.\n\n```csharp\n\u002F\u002F .NET Framework: create a sandbox domain (no longer works in .NET Core)\n\u002F\u002F var domain = AppDomain.CreateDomain(\"Sandbox\");\n\u002F\u002F domain.ExecuteAssembly(\"plugin.dll\");\n\n\u002F\u002F .NET 5+ equivalent: AssemblyLoadContext for plugin isolation\nvar context = new System.Runtime.Loader.AssemblyLoadContext(\"Plugin\", isCollectible: true);\nvar asm = context.LoadFromAssemblyPath(\"\u002Fpath\u002Fto\u002Fplugin.dll\");\nvar type = asm.GetType(\"Plugin.EntryPoint\");\n\u002F\u002F ... invoke methods via reflection ...\n\n\u002F\u002F Unload the context (and all assemblies in it) when done:\ncontext.Unload();\n\n\u002F\u002F True process isolation still requires separate processes:\n\u002F\u002F var proc = Process.Start(new ProcessStartInfo(\"dotnet\", \"plugin.dll\"));\n```\n\n**AssemblyLoadContext (ALC)** is the modern replacement: it provides assembly\nisolation and — when marked `isCollectible: true` — the ability to unload\nassemblies, which is crucial for hot-reload plugin systems.\n\n**Rule of thumb:** Forget AppDomains for .NET Core\u002F5+ work. Use\n`AssemblyLoadContext` for plugin loading and unloading. For true security\nisolation, use separate processes or containers.\n",{"id":82,"difficulty":14,"q":83,"a":84},"clr-exceptions","How does the CLR handle exceptions — what happens when an exception is thrown?","When an exception is thrown, the CLR unwinds the call stack looking for a matching\n`catch` handler. If none is found, the process terminates (with an unhandled-exception\nevent fired first). The mechanism relies on **Structured Exception Handling (SEH)**\nat the OS level on Windows and equivalent mechanisms on Linux\u002FmacOS.\n\n```csharp\nvoid Outer()\n{\n    try\n    {\n        Inner();\n    }\n    catch (InvalidOperationException ex)\n    {\n        \u002F\u002F CLR walked up the stack, found this handler\n        Console.WriteLine(ex.Message);\n    }\n    finally\n    {\n        \u002F\u002F Always runs — even if no catch matched, after the handler runs\n        Console.WriteLine(\"cleanup\");\n    }\n}\n\nvoid Inner()\n{\n    \u002F\u002F Note: rethrowing with 'throw' preserves the original stack trace\n    \u002F\u002F Rethrowing with 'throw ex' resets the stack trace — avoid that\n    try { DoWork(); }\n    catch { throw; } \u002F\u002F Good: preserves original stack trace\n}\n```\n\nThe CLR distinguishes **first-chance exceptions** (before any handler runs — useful\nfor debuggers) from **second-chance exceptions** (unhandled). The `finally` block\nruns after the matching `catch` block, or during stack unwinding if no handler\nis found in the current frame.\n\n**Rule of thumb:** Always rethrow with bare `throw`, never `throw ex` — the latter\ndestroys the original stack trace. Use `finally` for cleanup, not to swallow\nexceptions.\n",15,null,{"description":11},"How the .NET CLR executes managed code — IL, JIT compilation, garbage collection, the Common Type System, and the assembly model.","dotnet\u002Ffundamentals\u002Fclr-runtime","CLR Runtime","Fundamentals","fundamentals","2026-06-22","VAonIsovnk1_RDbbYm1pGfCC2Krl14sKnbIOEMux4bY",[96,97,100,104,108],{"subtopic":90,"path":21,"order":20},{"subtopic":98,"path":99,"order":12},"Value vs Reference Types","\u002Fdotnet\u002Ffundamentals\u002Fvalue-vs-reference-types",{"subtopic":101,"path":102,"order":103},"Generics","\u002Fdotnet\u002Ffundamentals\u002Fgenerics",3,{"subtopic":105,"path":106,"order":107},"LINQ","\u002Fdotnet\u002Ffundamentals\u002Flinq",4,{"subtopic":109,"path":110,"order":111},"Nullable Types","\u002Fdotnet\u002Ffundamentals\u002Fnullable-types",5,{"path":113,"title":114},"\u002Fblog\u002Fdotnet-clr-runtime-jit-managed-code","How the .NET CLR Works: JIT, GC, and the Assembly Model",1782244117788]