Wednesday 9 August 2017

Exception bubbling with reflection

When bubbling exceptions without reflection the outermost catch can "Silently" deal with the exception. Take a look at the following example.

using System;

namespace pc.ExceptionBubblingWithReflection {
    class Person {
        private string _name;
        public string Name
        {
            get { return _name; }
            set {
                if (String.IsNullOrEmpty(value))
                    throw new ArgumentNullException(nameof(Name));
                _name = value;
            }
        }
    }

    class Program {
        static Person person = new Person();
        static void Main(string[] args) {
            Console.WriteLine("Enter in a Name");

            try {
                person.Name = Console.ReadLine();
            }
            catch (ArgumentNullException anEx) {
                Console.WriteLine(anEx.Message);
            }

            //pause here
            Console.ReadKey();
        }
    }
}

now what do i mean by silent? I mean the debugger wont break the execution of your code when the exception is thrown in the setter of the Name property, you're probably thinking "No shit Sherlock"; well try the same principle but use reflection to set the Name property instead.

using System;

namespace pc.ExceptionBubblingWithReflection {
    class Person {
        private string _name;
        public string Name
        {
            get { return _name; }
            set {
                if (String.IsNullOrEmpty(value))
                    throw new ArgumentNullException(nameof(Name));
                _name = value;
            }
        }
    }

    class Program {
        static Person person = new Person();
        static void Main(string[] args)
        {
            Console.WriteLine("Enter in a Name");

            try {
                var pi = typeof(Person).GetProperty("Name");
                pi.SetValue(person, Console.ReadLine());
            }
            catch (ArgumentNullException ex) {
                Console.WriteLine(ex.Message);
            }

            //pause here
            Console.ReadKey();
        }
    }

}

Immediately you notice a TargetInvocationException, so let's deal with tat first. let's inspect the TargetInvocationException; when inspecting it we should notice that it's inner exception contains our ArgumentNullException, so let's modify our code to expose the message of our original exception.

class Program {
    static Person person = new Person();
   
    static void Main(string[] args)
    {
        Console.WriteLine("Enter in a Name");

        try {
            var pi = typeof(Person).GetProperty("Name");
            pi.SetValue(person, Console.ReadLine());
        }
        catch (TargetInvocationException tiEx) {
            Console.WriteLine(tiEx.InnerException.Message);
        }

        //pause here
        Console.ReadKey();
    }
}

Now by catching the TargetInvocatioException we are back on easy street, except for one bizarre behavior; our debugger still highlights our ArgumentNullException when we throw it.


if we continue our application executes as expected, however this can prove to be a pain when demoing an application or even when debugging. In comes the [DebuggerStepThrough] attribute to save the day, just add it to the setter of the Name attribute.

using System;
using System.Diagnostics;
using System.Reflection;

namespace pc.ExceptionBubblingWithReflection {
    class Person {
        private string _name;
        public string Name
        {
            get { return _name; }
            [DebuggerStepThrough]
            set {
                if (String.IsNullOrEmpty(value))
                    throw new ArgumentNullException(nameof(Name));
                _name = value;
            }
        }
    }

    class Program {
        static Person person = new Person();
   
        static void Main(string[] args)
        {
            Console.WriteLine("Enter in a Name");

            try {
                var pi = typeof(Person).GetProperty("Name");
                pi.SetValue(person, Console.ReadLine());
            }
            catch (TargetInvocationException tiEx) {
                Console.WriteLine(tiEx.InnerException.Message);
            }

            //pause here
            Console.ReadKey();
        }
    }
}

One caveat to be aware of is that by adding the [DebuggerStepThrough] attribute all exceptions will be stepped over in the setter by the debugger regardless if they are handled or not.