.NET 10 插件类型转换时进程崩溃,检查 AssemblyLoadContext.ToString
.NET 10.0.12 的 Linux 插件加载中,自定义加载上下文的 ToString 返回 null 可能触发原生崩溃,抛异常也可能覆盖原始转换错误。让诊断字符串安全返回,并另查跨上下文类型身份。
插件中的对象强制转换失败,本来应得到 InvalidCastException,进程却直接以段错误退出;或者日志里只剩自定义异常,原来的类型转换错误不见了。若应用自己派生了 AssemblyLoadContext,先检查它是否重写了 ToString()。
在 .NET 10.0.12、Linux x64 的具体故障中,加载上下文的 ToString() 返回 null,会在生成类型转换诊断名称时触发原生崩溃。若该方法改为抛出异常,抛出的异常又可能替代本应呈现的转换错误。启用程序集加载诊断时,也可能在更早的加载阶段走到同一名称获取路径。
类型转换出错后,运行时会生成诊断字符串;不安全的 ToString() 又可能在诊断阶段引发崩溃。仅在插件调用外包一层 catch (Exception),不能保证拦住原生崩溃。
诊断会调用加载上下文的 ToString
CLR 需要在错误信息中区分来自不同加载上下文的程序集。对于自定义上下文,相关原生实现会虚调用它的 ToString(),再把返回字符串复制到诊断名称中。
截至 2026 年 10 月 24 日,正式 .NET 10.0.13 的这条实现仍直接使用返回的字符串对象。不能把升级到 10.0.13 当成已经补上空返回保护的依据。这里的具体崩溃表现来自上述 Linux 组合,其他平台的表现还需按各自运行时和调用路径核对。
ToString() 本身就应返回有效、简短的字符串,不应返回 null 或抛出异常。不要在里面读取可能失效的插件对象、访问文件或执行其他可能失败的业务逻辑;日志、调试器和运行时诊断都可能在意料之外调用它。
移除覆盖,或沿用基类的安全表示
若重写只为调试时显示名称,最小处理是移除该覆盖,使用 AssemblyLoadContext 自身的表示。需要保留覆盖入口时,可直接调用基类实现。下面是使用 .NET 10、保留可回收加载上下文的类声明片段,不包含应用原有的程序集解析规则:
using System.Runtime.Loader;
public sealed class PluginLoadContext : AssemblyLoadContext
{
public PluginLoadContext() : base("Plugin", isCollectible: true) { }
public override string ToString() => base.ToString();
}
实际类若已有构造函数、Load 覆盖或解析事件,只调整 ToString(),不要用这个片段替换完整的加载器。也不要简单改成 return Name;:上下文名称可以为空,需要自定义格式时仍须提供确定的非空回退,并保证格式化本身不抛异常。
保存原加载器实现及部署包,在隔离环境重新走此前失败的插件加载与转换路径。若应用原先使用程序集绑定跟踪,还应在相同诊断条件下验证。目标是让错误以正常的托管异常呈现,进程能记录并处理失败,而非用关闭诊断日志暂时隐藏触发点。
恢复 InvalidCastException 后,继续处理类型身份
安全的 ToString() 只修正诊断行为,不会让两个加载上下文里的同名类型变成同一种类型。即使程序集名称、命名空间和接口名称看起来完全相同,分别加载的程序集实例仍可能形成不同的类型身份。
核对转换两端实际的 Type.Assembly,以及 AssemblyLoadContext.GetLoadContext(assembly) 返回的上下文。插件契约通常需要共享同一个契约程序集实例,或在边界采用明确的数据转换;不能仅比较类型名称字符串就强制转换。
因此,修改后的验收可能仍会收到正常的 InvalidCastException。这说明原始错误终于能被看见,随后还要按实际插件契约解决它。不要把不兼容对象强制吞成 null,也不要为了消除崩溃而将所有插件程序集不加区分地塞进默认上下文。


