How Rider's .NET Hot Reload Works: An Architecture Note
Rider's .NET Hot Reload pushes recompiled IL into a running debug session. This architecture note covers its requirements, minimal design, trust boundaries, operational checks, and failure modes.
02 Dec 2025, 07:59 UTC

Restarting a debug session to test a one-line change is the most common interruption in .NET development. Rider's Hot Reload feature eliminates most of those restarts by pushing recompiled code into the already-running process. Understanding how the pieces fit together — and where they break — helps you decide when to trust it and when to restart anyway.
Requirements the design must satisfy
The feature has four hard requirements:
- Detect source file changes quickly, without polling the whole solution.
- Recompile only what changed, not the full project — a full build per save would be slower than restarting.
- Deliver the updated IL (intermediate language, the bytecode the .NET runtime executes) into the live process without losing application state.
- Fail safely: if an edit can't be applied, the developer must be told rather than silently running stale code.
The smallest suitable design
Rider reuses three existing subsystems rather than building new ones:
- File watcher. Rider already watches project files for external changes. Hot Reload hooks into the same change stream, so there is no separate polling loop.
- Incremental MSBuild. On save, Rider invokes the normal build pipeline with incremental inputs, so only the affected project and its dependents recompile. The compiler emits updated IL plus metadata deltas rather than a full assembly swap.
- The runtime's Hot Reload API. Starting with .NET 6, the runtime itself exposes an apply-update mechanism (the same one
dotnet watchuses). Rider hands the IL and metadata deltas to the debugger, which applies them inside the debugee process. No code injection tricks, no AppDomain unloading.
This is the key architectural choice: the runtime applies the update, not the IDE. Rider is a coordinator — watch, build, hand off — which keeps the trust-sensitive part inside Microsoft's supported API surface.
Trust and data boundaries
Everything happens between two local processes: the Rider IDE process and the debugged application. The update payload travels over the existing debugger channel, the same channel already trusted to set breakpoints and inspect memory. There are no network calls, no telemetry of your source, and no third process involved.
Two practical consequences follow. First, Hot Reload only works in a debugging session (or an explicitly attached run), because the debugger channel is the transport. Second, anything that restricts the debugger — for example, hardened process-injection policies on a locked-down machine — will also block Hot Reload, and the fix belongs in the security policy, not in Rider.
Operational checks
To confirm the pipeline is healthy rather than assuming it is:
- Enable the feature under Settings → Build, Execution, Deployment → Hot Reload (exact path varies slightly by Rider version; search settings for "Hot Reload").
- Start a debugging session on a .NET 6 or later target.
- Make a supported edit — a method body change is the safest test — and save.
- Look for a "Hot Reload applied" confirmation in the Rider console or a notification. If you instead see a message that the change cannot be applied and a restart is required, the pipeline detected an unsupported edit.
- For deeper diagnosis, open Help → Show Log in Explorer and search the log for Hot Reload entries. These show whether the incremental build ran and whether the runtime accepted the delta.
A quick behavioral check: change a string literal inside a method that the running app executes repeatedly (a log line in a loop works well), save, and confirm the new text appears without the app restarting.
Failure modes and what causes them
| Symptom | Likely cause | Response |
|---|---|---|
| "Restart required" after an edit | Unsupported change: adding members, changing signatures, editing generics or lambdas in some runtime versions | Expected behavior; restart the session |
| Nothing happens on save | Feature disabled, project targets .NET Framework or .NET Core 3.1/5, or build failed silently | Check target framework and build output |
| Update applied but behavior unchanged | Edited code path not re-executed (e.g., constructor or startup code) | Trigger the code path again; restart if it's one-time init |
| IDE lag during rapid saves | Incremental build queue saturated by tight edit loops | Pause saving; consider disabling auto-save-triggered reload |
The design's fail-safe property matters here: every failure mode degrades to "restart the session," never to "silently run a mix of old and new code." If you ever doubt which code is running, the honest check is a restart.
Limitations worth knowing
Hot Reload requires a .NET 6+ runtime and portable PDB debug symbols; classic .NET Framework projects get no benefit. Supported edits are essentially method-body changes — structural edits (new types, new fields, signature changes) exceed what a running process can absorb. Changes to code that already ran and won't run again, such as initialization logic, appear to do nothing even when the update succeeds.
Conditions that would change this design
The architecture rests on two external dependencies, and it should be revisited if either moves:
- The runtime API. If a future .NET version deprecates or reshapes the apply-update mechanism, the handoff layer must be reworked — but the watcher and incremental build stages survive unchanged, which is the payoff of the thin-coordinator design.
- The build system. The design assumes MSBuild incremental compilation. A solution built with a different system (Bazel, for example) would need a new delta-producing stage, since the runtime API expects IL and metadata deltas in a specific form.
Until then, the design holds: watch, build incrementally, let the runtime apply the delta, and always be able to say no.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.