runtime: Add a way to opt out of TargetInvocationException wrapping on late-bound invokes.

Late-bound invokes through Reflection wrap exceptions thrown by the target in a TargetInvocationException. In many cases, this behavior is not desired and counter-productive. For those who want the actual exception, it requires unwrapping the TargetInvocationException to rethrow the inner exception and to retrieve the full call stack. The fact that every exception is “caught” hampers the normal debugging experience. It’d be useful to have a way to opt out of this wrapping.

We can do this without adding lots of new overloads by adding a new member to the BindingFlags enum: BindingFlag.DoNotWrapExceptions. Setting this bit would disable the wrapping behavior.

Here is a fiddle of the code sample included below: https://dotnetfiddle.net/o9qUht

public class Program
{
	public static void Main()
	{
		try {
			var bf = BindingFlags.Static | 
                            BindingFlags.Public | 
                            BindingFlags.InvokeMethod;

                        bf |= BindingFlags.DoNotWrapExceptions;

			typeof(Program).InvokeMember("LateBoundTarget", bf, null, null, null);

		} catch(TargetInvocationException e) {
			Console.WriteLine("fail");

		} catch(InvalidOperationException e) {
			Console.WriteLine("success");
		}
	}
	
	public static void LateBoundTarget() {
		throw new InvalidOperationException();
	}
}

Apis that would be affected:

    public static class Activator
    {
        public static object CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture);
        public static object CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes);
    }

    public class Type
    {
        public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args);
        public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, CultureInfo culture);
        public abstract object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters);
    }

    public class Assembly
    {
        public virtual object CreateInstance(string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes);
    }

    public abstract class ConstructorInfo : MethodBase
    {
        public abstract object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);
    }

    public abstract class MethodBase : MemberInfo
    {
        public abstract object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);
    }

    public abstract class PropertyInfo : MemberInfo
    {
        public abstract object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture);
        public abstract void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture);
    }

[EDIT] Added C# syntax highlight by @karelz

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 16 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Video

Looks good, the only suggestion is to rename PassExceptions to DoNotWrapExceptions. The rationale being that specifying ~PassExceptions could be read as catch-all.

var bf = BindingFlags.Public | BindingFlags.DoNotWrapExceptions;

That has nice didactic qualities too: Seeing DoNotWrapExceptions immediately teaches people that exceptions get wrapped!

Cleaned up the proposal a bit and promoted to api-ready-for-review.

We can tackle the CreateInstance<T>() thing separately if this gets approved.

Project N implementation is now merged.

CoreCLR implementation is still up for grabs. The exception wrapping seems to occur in various places in C++ code so this will require some C++ work.

FYI: The API review discussion was recorded - see https://youtu.be/VppFZYG-9cA?t=5527 (duration: 14 min)