纯c#项目不支持热更新,因.net运行时禁止卸载已加载程序集;所有方案均绕过限制:插件隔离、外部运行时(如hybridclr/ilruntime)或进程重启。

纯 C# 项目本身不支持热更新——.NET 运行时不允许在程序运行中卸载已加载的程序集(Assembly),这是根本限制。 所有“C# 热更新”方案,本质都是绕过这个限制:要么用插件机制隔离代码,要么依赖外部运行时(如 Unity 的 ILRuntime / HybridCLR),要么靠进程级重启模拟“热”的假象。
为什么直接 Assembly.LoadFrom 无法热更新?
你可能试过动态加载 DLL 并反射调用,但很快会遇到:System.IO.FileLoadException: 无法为程序集加载程序集,因为它已在使用中。这是因为 .NET Core/.NET 5+ 默认启用 AssemblyLoadContext.Default,加载进来的程序集无法卸载,重复加载同名/同版本程序集会失败。
- 即使改用
AssemblyLoadContext的非默认上下文(new AssemblyLoadContext(isCollectible: true)),也只能卸载该上下文内加载的程序集——但所有跨上下文的类型引用(比如你的主程序要传一个MyPluginConfig对象给插件)都会报InvalidCastException或RuntimeTypeHandle不匹配 - 静态字段、单例、事件订阅、未释放的资源(如
Timer、FileStream)极易在旧上下文中残留,导致内存泄漏或逻辑错乱 - 调试困难:堆栈跟踪里出现
DynamicClass或匿名上下文路径,IDE 断点失效
Unity 项目可用的真正热更新路径(非 MonoDevelop 时代老方案)
Unity 是目前 C# 热更新落地最成熟的场景,但必须明确区分目标构建平台:
- Android/iOS:只能走
HybridCLR(推荐)或ILRuntime。前者是微软官方支持的 AOT + 热更混合方案,能直接运行 C# 源码编译的 dll;后者需把热更代码编译为 IL 并托管执行,不支持泛型实例化、部分反射 API - Editor 模式下热重载(非运行时):用 Unity 2021.2+ 的
Hot Reload功能(基于 Roslyn Source Generators),仅限编辑器内修改脚本后自动重编译,不适用于打包后包体 - 绝对不要用
AssetBundle+ScriptableObject试图“热更逻辑”——AB 只能存数据和序列化资源,不能含可执行 C# 字节码
示例关键判断点:HybridCLR 要求热更 dll 必须用 Unity 同版本的 il2cpp 工具链重新生成,且主工程需开启 Enable Hot Reload 编译选项,否则运行时报 MissingMethodException。
纯 .NET 桌面/服务端如何“伪热更”?
没有银弹,只有权衡。常见可行做法是进程外隔离 + IPC:
- 把业务逻辑封装成独立
dotnet run子进程(如plugin-host.dll),主程序通过NamedPipeClientStream或gRPC通信;更新时杀掉旧进程、拉起新进程 - 用
Microsoft.Extensions.Hosting.IHostedService抽象出可启停的模块,配合文件系统监听(FileSystemWatcher)检测 dll 修改,触发StopAsync→ 替换文件 →StartAsync,但前提是模块不持有全局状态 - 放弃“热”,改用灰度发布:部署新版本服务实例,用反向代理(如 Nginx)切流,旧实例处理完存量请求后退出——这才是生产环境真正可靠的方式
注意:AssemblyLoadContext.Unload() 在 Windows 上仍不稳定(尤其涉及 P/Invoke 或 COM 组件时),.NET 6+ 虽标记为稳定,但实际中大量用户反馈卸载后 GC 不及时,导致句柄泄漏。
热更新不是功能开关,而是架构决策。它强制你把状态外置(Redis/DB)、接口抽象化(IModule)、错误容忍设计(超时重试、降级返回)。哪怕只为了开发期省一次 F5,也得先接受这些约束。










