v0.2.5 更新日志
发布日期:2026 年 5 月 7 日
v0.2.5 是 v0.2.x 的修复与功能迭代版本,包含一处问题修复(插件 ALC 被 GC 提前回收)和一项增强功能(插件配置文件增量合并)。
v0.2.5 不包含任何破坏性变更,Contracts 未做改动,现有部署和插件无需任何修改即可升级。
修复内容
修复:插件 AssemblyLoadContext 被 GC 提前回收导致的服务注册失败
在特定条件下,插件加载后进入服务注册阶段(RegisterServices)时,会抛出 System.Reflection.ReflectionTypeLoadException 或插件类型无法解析的异常。
根因分析:
插件使用 isCollectible: true 的 AssemblyLoadContext(ALC)实现隔离加载。v0.2.4 及之前版本中,PluginLoader 实例是 ApplicationHost.RunAsync 方法的局部变量,方法执行完毕后失去所有强引用。当 GC 在服务注册阶段触发回收时,这些 ALC 会被标记为 unloading,导致从插件程序集加载类型失败。
修复方式:
PluginLoader由局部变量提升为ApplicationHost的实例字段_pluginLoader,在RunAsync的整个生命周期内持有强引用PluginLoader内部新增_loadContexts列表,追踪所有已创建的PluginLoadContext实例,确保在服务注册完成前 ALC 保持 alive 状态- 新增
UnloadAll()方法,提供在主程序退出时清理所有 ALC 的入口
| 变更 | 文件 |
|---|---|
_pluginLoader 实例字段 | ApplicationHost.cs |
_loadContexts 追踪列表 + LoadContexts 属性 + UnloadAll() | PluginLoader.cs |
新增内容
新增:插件配置文件增量合并
v0.2.5 之前,EnsurePluginConfigFile 仅在插件配置文件不存在时从 DefaultConfig 生成默认配置。如果插件开发者在新版本中为 DefaultConfig 新增了配置键,现有用户的 config/{插件名}.json 永远不会感知这些新增的键,除非用户手动删除配置文件后重启。
v0.2.5 对配置文件的生成逻辑进行了全面增强:
旧行为:
配置文件是否存在?
├── 不存在 → 从 DefaultConfig 生成,写入
└── 已存在 → 不做任何操作,即使 DefaultConfig 新增了键也不合并新行为:
配置文件是否存在?
├── 不存在 → 从 DefaultConfig 生成,写入
└── 已存在 → 读取现有配置,与 DefaultConfig 递归合并缺失的键,写回合并规则如下:
| 场景 | 行为 |
|---|---|
| DefaultConfig 中的键在现有配置中不存在 | 新增到现有配置中(使用 DefaultConfig 的键名大小写) |
| DefaultConfig 中的键在现有配置中已存在(大小写完全匹配) | 值类型或数组 → 保留现有值,不覆盖;双方都是嵌套对象 → 递归合并缺失的子键 |
| DefaultConfig 中的键在现有配置中存在但大小写不同 | 保留现有键名大小写,不重复添加;嵌套对象同样递归合并 |
| 现有配置中多余的键(DefaultConfig 不包含) | 保留,不删除 |
这一增强使得插件开发者可以在 DefaultConfig 中放心添加新配置项,用户的现有配置文件会在启动时自动增量补充,既不会覆盖已有的用户配置,也不会删除用户的额外配置项。
csharp
// 示例:某插件 v1.0 的 DefaultConfig
public object? DefaultConfig => new MySettings { Host = "localhost" };
// 用户现有 config/my.plugin.json:{ "Host": "192.168.1.100" }
// 某插件 v1.1 的 DefaultConfig(新增 Port 字段)
public object? DefaultConfig => new MySettings { Host = "localhost", Port = 8080 };
// 升级后首次启动,config/my.plugin.json 自动变为:
// { "Host": "192.168.1.100", "Port": 8080 }
// ↑ Host 保留用户的值,Port 从 DefaultConfig 自动补充兼容性说明
- 无破坏性变更,直接替换主程序二进制文件即可完成升级
- Contracts 未变更,插件无需重新编译,无需更新 NuGet 包引用
- 配置文件增量合并在后台自动完成,无需用户或开发者手动介入
- 如果插件开发者不希望新增的键被自动补充到用户配置中,应在
DefaultConfig中保持该键为null或不包含该属性