使用Flee库调用GetType()触发System.InvalidProgramException问题求助
DateTimeA.GetType().Name表达式执行失败 问题概述
在验证表达式正确性及对应IL合规性时,触发错误:System.InvalidProgramException : Common Language Runtime detected an invalid program。
待解析表达式为DateTimeA.GetType().Name,预期结果格式为String;DateTimeA.GetType().Name;String。该用例来自Flee库的ValidExpressions.txt,重新添加测试后执行失败。
生成的IL指令
生成的IL指令如下,总长度17:
ldarg.0 ldfld callvirt callvirt ret
相关背景信息
- 当前运行环境为**.NET Standard**,其中
AssemblyBuilderAccessor的Save选项已被移除。 - 微软关于
GetType()的官方说明:
GetType仅适用于从磁盘加载的程序集。若调用GetType查找使用System.Reflection.Emit服务定义的动态程序集中的类型,可能出现不一致行为。行为取决于动态程序集是否持久化(即使用System.Reflection.Emit.AssemblyBuilderAccess枚举的RunAndSave或Save访问模式创建)。若动态程序集已持久化且在调用GetType前已写入磁盘,加载器会找到磁盘上的已保存程序集,加载并从中检索类型;若调用GetType时程序集未保存到磁盘,方法将返回null。GetType不识别临时动态程序集,因此调用GetType检索临时动态程序集中的类型会返回null。
要在动态模块上使用GetType,请订阅AppDomain.AssemblyResolve事件并在保存前调用GetType,否则内存中将存在两份程序集副本。
原因分析
- 动态程序集的限制:Flee库通过Reflection.Emit生成动态IL执行表达式,在.NET Standard环境下,动态程序集默认仅支持
Run模式(无Save选项),属于临时动态程序集。根据微软说明,GetType()无法识别此类临时动态程序集中的类型,调用后可能返回null,导致后续访问Name属性时IL指令执行无效,触发InvalidProgramException。 - IL指令的逻辑缺陷:生成的IL中连续使用
callvirt调用GetType()和Name属性,由于GetType()返回null或无效类型引用,后续的callvirt调用无法找到合法的目标方法/属性,导致CLR判定程序无效。
解决方案
1. 替换GetType()为类型元数据直接获取
在表达式编译阶段,若已知DateTimeA的类型(如编译时可确定变量类型),将表达式DateTimeA.GetType().Name替换为typeof(DateTimeA).Name。Flee库需修改表达式解析逻辑,对GetType()调用做特殊处理:当调用对象的类型在编译阶段可知时,直接加载该类型的Type对象,而非生成调用实例GetType()的IL指令。
2. 修改IL生成逻辑,规避GetType()调用
对于无法提前确定类型的变量,生成IL时避免调用实例的GetType(),改用Type.GetTypeHandle获取类型句柄,再转换为Type对象,示例IL逻辑如下:
ldarg.0 // 加载变量实例 call System.Type::GetTypeHandle(object) call System.Type::GetTypeFromHandle(System.RuntimeTypeHandle) callvirt System.Type::get_Name() ret
3. 兼容动态程序集的类型解析
若必须保留GetType()调用,可在动态程序集创建时(若环境允许)切换到支持持久化的模式,但.NET Standard下此选项不可用。替代方案是订阅AppDomain.AssemblyResolve事件,在GetType()调用前确保动态程序集能被解析,但此方法可能带来内存中存在重复程序集的问题,需谨慎使用。
4. 调整Flee库的测试用例
若上述修改成本较高,可临时调整测试用例,将DateTimeA.GetType().Name替换为等效的合法表达式,如直接返回类型名称字符串,以绕过此兼容性问题。
内容的提问来源于stack exchange,提问作者bob123123123123

