C# 跨类调用Assembly.ReflectionOnlyLoadFrom加载DLL抛出异常问题
问题根因
问题完全出在Marshaller类上多余的[Serializable]特性,和反射加载上下文的封送逻辑冲突:
Assembly.ReflectionOnlyLoadFrom()会把目标DLL加载到独立的仅反射加载上下文,和程序默认运行的执行上下文完全隔离,返回的Assembly实例是绑定在这个反射上下文里的对象,不能直接跨上下文传递。- 给类加
[Serializable]标记后,运行时会认为这个类的方法调用可能存在跨应用域/跨上下文的序列化传递场景,因此在LoadAssembly()方法执行完返回结果时,不会直接把对象引用交回给调用方,而是会尝试对返回的Assembly对象做跨上下文封送。这个封送过程会跑到默认执行上下文里重新找目标DLL,不会复用反射上下文里已经加载好的程序集,自然就会抛出文件找不到的异常。 - 这个封送步骤是在
LoadAssembly()方法内部代码执行完、控制权返回给调用方的瞬间触发的,所以异常不会落在方法里的ReflectionOnlyLoadFrom那行,只会落在调用方接收返回值的Assembly assembly = marshaller.LoadAssembly();这行。 - 把加载逻辑直接写在
Loader类里时,调用栈上没有序列化带来的上下文边界,返回的Assembly直接在当前上下文用,不会触发额外的解析逻辑,所以能正常跑。 - 清理解决方案、重新生成这类操作只处理编译产物,根本碰不到运行时的上下文封送逻辑,当然解决不了问题。
修复方法
选任意一种即可,优先选第一种:
- 直接删掉
Marshaller类上的[Serializable]特性:这个类没有任何跨应用域、跨上下文序列化的使用场景,这个特性纯属多余。删掉之后上下文封送边界消失,代码行为和直接把逻辑写在Loader里完全一致:
public class Marshaller { internal Assembly LoadAssembly() { return Assembly.ReflectionOnlyLoadFrom("C:\\Users\\OLNO\\Downloads\\SkinProject1.dll"); } }
- 如果确实有需求要保留
[Serializable]特性(比如后续要跨AppDomain传递Marshaller实例),就不要直接返回仅反射上下文的Assembly对象,改成返回目标DLL的完整路径字符串,在调用方所在的上下文再执行加载,避免跨上下文传递绑定在特定上下文的对象。 - 也可以通过给当前AppDomain注册
ReflectionOnlyAssemblyResolve事件,手动处理封送阶段的程序集解析逻辑,返回已经加载到反射上下文的对应程序集,但这个方案实现繁琐,没有特殊需求不建议用。
内容的提问来源于stack exchange,提问作者Oleksandr Novik
相关产品推荐
相关产品推荐

