反射调用 API 异常变更

调用反射调用 API 时引发的异常已变更。

旧行为

新行为

从 NET 7 开始:

引入的版本

.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