反射调用 API 异常变更
调用反射调用 API 时引发的异常已变更。
旧行为
以前,当调用的方法通过引用返回值返回
null
时,将引发 NullReferenceException。对于构造函数,将引发以下异常:
- 暂时性异常,包括 OutOfMemoryException。
- 为数组的长度参数传递负值时返回 OverflowException。
在没有
ref
修饰符的情况下为类似 byref 的参数传递null
时(即按值传递),不会引发异常,运行时用默认值替换 null 值。
新行为
从 NET 7 开始:
在验证初始
Invoke()
参数后,会在所有情况下引发 TargetInvocationException,而不是引发原始异常(包括旧行为中提及的 NullReferenceException 和 OutOfMemoryException)。 内部异常包含原始异常。当在类似 byref 的参数被声明为“按值”(即该参数没有
ref
修饰符)的情况下为其传递null
时,将引发 NotSupportedException。 对于通过引用传递参数的相关情况(即该参数具有ref
修饰符),旧行为和新行为是相同的:将引发 NotSupportedException。
引入的版本
.NET 7
中断性变更的类型
此项更改可能会影响二进制兼容性。
更改原因
引发 TargetInvocationException 而不是原始异常会使体验更加一致。 它正确地对由传入参数验证(未使用 TargetInvocationException 包装)引发的异常与由于目标方法实现(包装)而引发的异常进行分层。 一致规则跨 CLR 和 Invoke
API 的不同实现提供更一致的体验。
将类似 byref 的类型传递给 Invoke()
API 时引发的 NotSupportedException 的变更修复了原始实现的疏忽,即它不会引发异常。 原始实现个人的感觉是 Invoke()
API 支持 ref struct
类型,而实际上并不支持。 由于当前 Invoke()
API 使用 System.Object 作为参数类型,并且 ref struct
类型无法将装箱到 System.Object,因此这是一种不支持的方案。
建议的操作
如果在调用 Invoke()
时不使用 BindingFlags.DoNotWrapExceptions,并且针对 TargetInvocationException 以外的异常在 Invoke()
API 周围使用 catch
语句,请考虑更改或删除这些 catch
语句。 调用的结果是,将不再引发其他异常。 但是,如果从尝试调用目标方法之前发生的参数验证中捕获异常,则应保留这些 catch
语句。 在尝试调用之前验证的无效参数会在没有使用 TargetInvocationException 包装的情况下被引发,并且不会更改语义。
请考虑使用 BindingFlags.DoNotWrapExceptions,这样永远不会引发 TargetInvocationException。 在这种情况下,原始异常不会被 TargetInvocationException 包装. 在大多数情况下,不包装异常可以增加诊断实际问题的机会,因为并非所有异常报告工具都显示内部异常。 此外,通过使用 BindingFlags.DoNotWrapExceptions,将引发与直接调用方法(不反射)时相同的异常。 在大多数情况下,这是可取的,因为是否选择使用反射取决于个人意愿,同时是不需要向调用方展示的实现详细信息。
在极少数情况下,需要通过反射将默认值传递给一个方法,反射包含一个“按值”传递的类似 byref 的参数,你可以添加包装器方法,该方法省略该参数并使用该参数的默认值调用目标方法。
受影响的 API
- System.Reflection.MethodBase.Invoke(Object, Object[])
- System.Reflection.MethodBase.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
- System.Reflection.ConstructorInfo.Invoke(Object[])
- System.Reflection.ConstructorInfo.Invoke(BindingFlags, Binder, Object[], CultureInfo)
- System.Reflection.PropertyInfo.GetValue(Object)
- System.Reflection.PropertyInfo.GetValue(Object, Object[])
- System.Reflection.PropertyInfo.GetValue(Object, BindingFlags, Binder, Object[], CultureInfo)
- System.Reflection.PropertyInfo.SetValue(Object, Object)
- System.Reflection.PropertyInfo.SetValue(Object, Object, Object[])
- System.Reflection.PropertyInfo.SetValue(Object, Object, BindingFlags, Binder, Object[], CultureInfo)
- System.Reflection.Emit.DynamicMethod.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
- System.Activator.CreateInstance(Type, Object[])
- System.Activator.CreateInstance(Type, Object[], Object[])
- System.Activator.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo)
- System.Activator.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[])
- System.Activator.CreateInstance(String, String, Boolean, BindingFlags, Binder, Object[], CultureInfo, Object[])
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈