Changes to reflection invoke API exceptions
The exceptions thrown when calling reflection invoke APIs have changed.
Previous behavior
Previously, when an invoked method that returns a value by reference returned
null
, a NullReferenceException was thrown.For constructors, the following exceptions were thrown:
- Transient exceptions, including OutOfMemoryException.
- An OverflowException when a negative value was passed for the length parameter of an array.
When
null
was passed for a byref-like parameter without theref
modifier (that is, passed by value), no exception was thrown, and the runtime substituted a default value for the null value.
New behavior
Starting in .NET 7:
Instead of throwing the originating exception (including NullReferenceException and OutOfMemoryException mentioned in Previous behavior), TargetInvocationException is thrown in all cases after the initial
Invoke()
parameters are validated. The inner exception contains the originating exception.NotSupportedException is thrown when
null
is passed for a byref-like parameter when the parameter is declared as being "by value" (that is, it has noref
modifier). For the related case when the parameter is passed by reference (that is, it has theref
modifier), the previous and new behavior are the same: a NotSupportedException is thrown.
Version introduced
.NET 7
Type of breaking change
This change can affect binary compatibility.
Reason for change
Throwing TargetInvocationException instead of the originating exception makes the experience more consistent. It properly layers exceptions caused by the validation of the incoming parameters (which aren't wrapped with TargetInvocationException) versus exceptions thrown due to the implementation of the target method (which are wrapped). Consistent rules provide more consistent experiences across different implementations of the CLR and of Invoke
APIs.
The change to throw NotSupportedException when a byref-like type is passed to an Invoke()
API fixes an oversight of the original implementation, which did not throw. The original implementation gave the appearance that ref struct
types are supported by the Invoke()
APIs, when they aren't. Since the current Invoke()
APIs use System.Object for parameter types, and a ref struct
type can't be boxed to System.Object, it's an unsupported scenario.
Recommended action
If you don't use BindingFlags.DoNotWrapExceptions when calling Invoke()
, and you have catch
statements around the Invoke()
APIs for exceptions other than TargetInvocationException, consider changing or removing those catch
statements. The other exceptions will no longer be thrown as a result of the invocation. If, however, you're catching exceptions from argument validation that happens before attempting to invoke the target method, you should keep those catch
statements. Invalid arguments that are validated before attempting to invoke are thrown without being wrapped with TargetInvocationException and did not change semantics.
Consider using BindingFlags.DoNotWrapExceptions so that TargetInvocationException is never thrown. In this case, the originating exception won't be wrapped by a TargetInvocationException. In most cases, not wrapping the exception improves the chances of the diagnosing the actual issue since not all exception reporting tools display the inner exception. In addition, by using BindingFlags.DoNotWrapExceptions, the same exceptions will be thrown as when calling the method directly (without reflection). This is desirable in most cases, since the choice of whether reflection is used or not can be arbitrary or an implementation detail that does not need to surface to the caller.
In the rare case that you need to pass a default value to a method through reflection that contains a byref-like parameter that's passed "by value", you can add a wrapper method that omits the parameter and calls the target method with a default value for that parameter.
Affected APIs
- 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[])