调用 Assembly.GetType 时出现间歇性 TypeLoadException - 程序集中类型中的方法没有实现(仅有时)
在我的项目中,有一个带有抽象方法的抽象基类。 我们基于模式生成实现,然后通过 Assembly.LoadFrom
反射加载这些实现,然后调用 Assembly.GetType
来获取尚未定义的接口的具体实现另一个 DLL。
不同项目(DLL 文件)的结构:
Schema
- 包含类型定义Base
- 具有由所有生成的实现共享的基类Generate
-生成的类型,实现Base
中的抽象基类和Schema
中的接口。
public interface IExample
{
//Some methods here, irrelevant to the problem
}
public abstract Base
{
protected abstract void SomeMethod(SomeType someArg); //This method is the method the exception specifies, but I suspect it's only because it's the first one in the file.
//More methods like the above, and some non-abstract/virtual methods too
}
public class Generated : Base, IExample
{
protected override void SomeMethod(SomeType someArg)
{
//Some implementation here
}
//More content here, not all of it being from either the interface or the Base type
}
var asm = Assembly.LoadFrom(path);
asm.GetType("SomeNameSpace.Generated"); //This is where it fails
这种方法工作得很好,直到 Base 项目在不相关的区域进行了更新并且其版本得到了改进。
生成的实现由它实现的接口类型请求。 (泛型涉及类型定义,不确定这是否真的相关)
现在通常这将是一个简单的情况“哦,你只需要重新编译它并再次包含它”,但令人兴奋的部分是这只是有时会失败!
大约一半的时间,它就有效了。另一半,它抛出 TypeLoadException ,认为该方法没有实现。 通常我预计它总是会失败,但事实并非如此。
当然,包含新编译的生成的 DLL 可以完全避免这种情况。但我希望能够更新架构和基础项目,而不需要整个事情。 (它适用于仅包含相关文件的“服务包”样式软件更新)
需要明确的是,所涉及的类型都没有被修改。不会出现“哦,我刚刚向方法添加了一个可选参数,因此它是相同的方法”错误。
唯一的更改是文件的其他部分。 Base
位于一个大 DLL 中,其中包含许多不相关的实用程序。 Base
、IExample
和生成的Generate
仍然完全相同。如果是某些版本解析造成严重破坏,我预计会出现问题。
遗憾的是,这不是一个我可以打包成可重现示例的简单小项目,而是一个具有许多层和模式的相当复杂的程序。我不确定如果我尝试的话是否可以重现此问题,我依赖于当程序开始加载内容并调用代码时它会失败。 (创建 Generated
实例的相关反射代码)
异常消息如下所示:(名称已更改以匹配示例代码,是的,其程序集版本是 0.0.0.0)
System.TypeLoadException: Method 'SomeMethod' in type 'SomeNameSpace.Generated' from assembly 'SomeNameSpace.Generated, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. at System.Reflection.RuntimeAssembly.GetType(RuntimeAssembly assembly, String name, Boolean throwOnError, Boolean ignoreCase, ObjectHandleOnStack type) at System.Reflection.RuntimeAssembly.GetType(String name, Boolean throwOnError, Boolean ignoreCase) at SomeMoreOfOurOwn.CallingTheReflection.AndFailing(Factory factory)
如上所述,这是一个尝试相同的事情并希望得到不同结果的情况是有效的,因为这个问题不会有一半的时间发生。 (并不是因为代码没有被调用,代码库是基于此模式构建的一切)
唯一可预测的是,它总是在同一件事上失败,但我认为这只是因为它确定性地在所有未更新的生成文件中首先执行该操作。
这是一个 .NET Framework 4.7.1 项目。
In my project, there is an abstract base class with an abstract method.
We generate implementations based on a schema and later load these by reflection with Assembly.LoadFrom
and then call Assembly.GetType
to get a concrete implementation of an interface which is defined in yet another DLL.
The structure of the different projects (DLL files):
Schema
- Containing type definitionBase
- Has the base class shared by all generated implementationsGenerated
- A generated type that implements the abstract base class fromBase
and the interface fromSchema
.
public interface IExample
{
//Some methods here, irrelevant to the problem
}
public abstract Base
{
protected abstract void SomeMethod(SomeType someArg); //This method is the method the exception specifies, but I suspect it's only because it's the first one in the file.
//More methods like the above, and some non-abstract/virtual methods too
}
public class Generated : Base, IExample
{
protected override void SomeMethod(SomeType someArg)
{
//Some implementation here
}
//More content here, not all of it being from either the interface or the Base type
}
var asm = Assembly.LoadFrom(path);
asm.GetType("SomeNameSpace.Generated"); //This is where it fails
This worked fine until the Base project was updated in an unrelated area and its version advanced.
The generated implementation is being requested by the interface type it implements. (Generics are involved in the type definition, not sure if that's really relevant)
Now normally this would be a simple case of "Oh you just need to re-compile it and include it again" but the exciting part is that this only sometimes fails!
Roughly half the time, it just works. The other half, it throws the TypeLoadException arguing the method doesn't have an implementation.
Normally I'd expect it to always fail, but that's not the case.
Of course, including the newly compiled Generated DLL avoids this entirely. But I'm looking to be able to update both the Schema and Base projects without requiring the whole thing. (It's for 'service pack' style software updates only containing the relevant files)
Just to be clear, none of the involved types were modified. No "Oh I just added an optional argument to a method so it's the same method" mistakes.
The only changes are in other parts of the files. Base
is in a big DLL with lots of unrelated utility in it. Base
, IExample
, and the resulting Generated
are still exactly the same. If it was some version resolution causing havoc, I'd expect problems.
This is sadly not a simple small project I could pack up into a reproducible example, but a rather complicated program with many layers and patterns. I'm not sure I could reproduce this if I tried, I'm relying on it failing when the program starts loading things and calling code. (The relevant reflection code that creates an instance of Generated
)
The exception message looks like this: (names changed to match example code, and yes its assembly version is 0.0.0.0)
System.TypeLoadException: Method 'SomeMethod' in type 'SomeNameSpace.Generated' from assembly 'SomeNameSpace.Generated, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. at System.Reflection.RuntimeAssembly.GetType(RuntimeAssembly assembly, String name, Boolean throwOnError, Boolean ignoreCase, ObjectHandleOnStack type) at System.Reflection.RuntimeAssembly.GetType(String name, Boolean throwOnError, Boolean ignoreCase) at SomeMoreOfOurOwn.CallingTheReflection.AndFailing(Factory factory)
As mentioned, this is a case where trying the same thing and hoping for different results works, because this issue doesn't happen half the time. (And not due to the code not being called, the codebase is built on this pattern for everything)
The only predictable thing is that it always fails on the same thing, but I think that's just because it's deterministically doing that one thing first amongst all of the non-updated generated files.
This is a .NET Framework 4.7.1 project.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
更新是因为一年前的缓解措施实际上并不相关,只是顺便帮助解决了竞争条件。
事实证明,真正的罪魁祸首是使用原始字节加载不同程序集的代码的不同部分。
问题在于,这种情况在不同的线程中多次发生,加载程序集的副本,该程序集具有其他反射加载程序集的一些基类。
我们通过向缓存这些程序集的字典添加双重检查锁定来修复它,问题就消失了。
Update because the mitigation from a year ago was actually not relevant and only incidentally helped with a race condition.
Turns out the real culprit is a different part of the code that loads a different assembly using raw bytes.
The problem was that this happened multiple times from different threads, loading copies of the assembly, which had some base classes for the other reflection loaded assemblies.
We fixed it by adding double-checked locking to the dictionary that cached these assemblies and the problem went away.