The .NET Runtime
How your code actually runs
What is .NET
Read `dotnet --info` and decode every version number to the layer it reports on, pick SDK or runtime-only for a given machine role, and place a .NET version on the LTS/STS support timeline.
The CLR & Intermediate Language
Trace a method through both translations, C# to IL at build and IL to native at run, read its opcodes off the evaluation stack, find the method names in a .dll's metadata tables, and see why C# emits callvirt for a non-virtual call.
JIT Compilation
Follow one method from its load-time stub to native code, watch a hot method get recompiled from Tier 0 to an optimized Tier 1 mid-run, and pin first-request latency on the JIT compiling every method in the call chain on first hit.
Native AOT
Publish one program two ways to see the native build drop the JIT and the IL, trace why a reflection call the trimmer cannot follow crashes at run time, prove a type sealed because nothing can subclass it, and place a workload where startup or steady-state decides between Native AOT, ReadyToRun, and the JIT.
Assemblies & NuGet
Resolve a Version="8.0.0" constraint to the build NuGet actually picks under each of the four rules, locate the resolved graph in obj/project.assets.json instead of the .csproj, and separate a package's id-plus-SemVer from the four-part assembly identity the CLR loads by.
Memory & the Type System
Stack, heap, GC, and the type machinery
Stack vs Heap
Predict whether a variable's bytes land on the stack or the heap from its lifetime, not its type, and trace why heap residency is what costs you at collection time.
Value Types vs Reference Types
Trace what a single `=` copies for a struct versus a class, a whole value or just a shared handle, and predict the aliasing, boxing, and default-equality behavior that follows from that one keyword.
The Garbage Collector
Follow the collector from the roots it marks to the survivors it promotes and compacts to new addresses, and predict how allocation rate, not a timer, sets how often it runs.
How Generics Work
Read a constructed generic's type argument back at run time, the move Java's erasure forbids, then connect it to why `List<int>` never boxes and why `Counter<int>` and `Counter<string>` keep separate statics.
Span<T> & Memory<T>
Slice a string or array into a zero-copy view instead of allocating a fresh copy, and choose Span<T> for synchronous hot paths but Memory<T> the moment a buffer must survive an await.
Nullable Reference Types
Separate the `?` that is an erased compiler note on `string` from the `?` that builds a real `Nullable<int>` struct, and read nullable warnings as compile-time intent the build can ship past, not a runtime guarantee.
Dependency Injection
The pattern that wires everything together
Why Dependency Injection
Trace one hard-coded `new` out of a constructor and watch it unlock testing, swapping, and the dependency inversion principle, then meet the assembler .NET builds in.
The .NET DI Container
Register services into an IServiceCollection, build it into a provider, and watch one GetRequiredService call walk the constructor graph and assemble the whole object tree, failing loud when a registration is missing.
Service Lifetimes
Pick Transient, Scoped, or Singleton from how a service holds state, predict how many instances exist across requests, and catch the captive dependency that scope validation only flags in Development.
Registration Patterns
Hand the container a factory delegate, cover every closed type with one open generic line, and reach for TryAdd and TryAddEnumerable so a library's default yields to the app, each just a different shape of descriptor row.
Keyed Services
Tag each registration of one interface with a key, then resolve the one you mean with FromKeyedServices or GetRequiredKeyedService, and watch keyed and keyless rows ignore each other's lookups.
DI Anti-Patterns
Spot four DI traps that compile and run: a constructor hiding its real dependency behind IServiceProvider, a singleton freezing a per-request service, a parameter list outgrowing its class, and a BuildServiceProvider call minting a second set of singletons, then fix each.
Configuration & Options
Strongly-typed config done right
The Configuration System
Trace one config key through a stack of providers, watch the last provider added overwrite the rest, and see why an environment variable silently beats committed appsettings.json while `__` replaces the colon in env-var names.
The Options Pattern
Bind a config section onto a typed class, then choose between IOptions, IOptionsSnapshot, and IOptionsMonitor by how fresh the value must be, and see why a scoped snapshot cannot live inside a singleton.
Options Validation
Attach validation rules to bound options with data annotations or a cross-field check, move the failure from the first request to startup, and read every problem from one OptionsValidationException.
Environments & Feature Flags
Flip `ASPNETCORE_ENVIRONMENT` to watch a second appsettings file layer on and `IsDevelopment()` branches switch, then contrast that frozen deploy-time string with feature flags read per request through `IsEnabledAsync`, toggled by percentage or time-window filters without a redeploy.
Secrets Management
Move a database password out of committed appsettings.json into a plaintext User Secrets file the repo never sees, then watch the same config["Db:Password"] read null in Production until a startup-loaded Key Vault or Secrets Manager provider supplies it, ordered by the same last-writer-wins stack.
Build & Distribution
Restore, metaproj, source mapping: the supply chain
Restore Pipeline
Split a single `dotnet build` into its restore-then-compile halves, follow the two restore phases (an MSBuild collection pass running CollectPackageReferences into the dgspec, then a NuGet resolver writing obj/project.assets.json), and show why deleting obj/ still restores fast while a matching dgspec hash skips restore entirely.
MSBuild Targets & Hooks
Trace which MSBuild targets fire under project restore versus solution restore, watch a BeforeTargets="Restore" hook silently no-op when a solution synthesizes its .sln.metaproj, and bind restore-time logic to CollectPackageReferences so it fires on every entry point.
Asset Flow & References
Map an `IncludeAssets`/`ExcludeAssets`/`PrivateAssets` combination to which asset streams your build consumes versus which flow to a downstream consumer, and decide when a build-time-only package (protoc tools, analyzers, source generators) earns `PrivateAssets=all`.
Sources, Feeds & Mapping
Race two feeds for the same package id and watch order get ignored, compose a repo nuget.config whose <clear /> stops it inheriting machine-level sources, and pin each id to a feed with packageSourceMapping so a public impostor of a private package is never queried.