Tips & Tricks

Try..Catch for .NET Interoperability

While it may be a cold day in hell before we see any TRY..CATCH constructs in pure C/AL, we are all far more lucky when it comes to .NET interoperability. In this blog post I’ll (re)present the same concept I demonstrated during NAV TechDays 2013 last year in Antwerp, because I am quite sure this nifty little trick got lost under piles of other posts on this blog.

So, let’s learn how to do try..catch..finally for .NET interoperability C/AL code, using mostly C/AL code.

I say “mostly”, because to implement a try..catch..finally construct in C/AL we need an external .NET assembly.

Let’s start by taking a look at a simple chunk of C/AL code:

image

Nice, isn’t it?

(If it wasn’t obvious, F is of the System.IO.FileInfo type.)

Now, this piece of code can fail for whatever reason.  File may not be found. File may be in use. File my be inaccessible due to security permissions. Probably something else as well.

If you want to catch that error, though, you are out of options. One option is to call this codeunit from another object with the IF CODEUNIT.RUN construct, which is typically frowned upon because it complicates the code unnecessarily, and it demands a new codeunit object for every possible catchable operation and always causes licensing implications.

We don’t have any TRY..CATCH in C/AL, but we do have somethingelse: .NET interoperability, which has events. For a moment, consider this code:

image

Let’s theorize for a moment. Since C/AL fully supports .NET events, and since .NET supports try..catch..finally syntax, we can write a simple class that has two events: Catch and Finally. It can execute anything within a try block, and then in its catch and finally blocks it can raise the Catch and Finally events, respectively. Just like in the code above.

But of course not, the code above cannot just work. For it to work, we must make sure that whatever C/AL code we want to raise the Catch or Finally events for must run within a try..catch..finally block inside the TryCatchHelper class. So, how do we make our F.Delete method call execute inside this helper class’ try..catch..finally block?

Reflection.

Instead of calling our Delete method directly on the F object, we insteaad pass the object instance, the method name, and any parameters to a method on the TryCatchHelper class, which then executes this method on our behalf within a try..catch..finally block.

This is what this C/AL code should look like, with all the “magic” replaced by the actual code:

image

We first instantiate the list of parameters (the List object), then we instantiate the TryCatchHelper class, then we instantiate the F object. And then, instead of calling F.Delete, we actually call the TryCatchHelper’s TryCatchFinally method. Inside this method, we get hold of the MethodInfo instance for the method named ‘Delete’, call it on the object F, and pass to it the parameter list. Simple.

This TryCatchHelper class needs to have three methods: TryCatch, TryFinally, and TryCatchFinally, to support all three possibly try..catch..finally constructs.

And, finally, this is the TryCatchFinally class in C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace TechDays2013
{
    public delegate void CatchEventHandler(object sender, MethodInfo method, Exception e);

    public delegate void FinallyEventHandler(object sender, MethodInfo method);

    public class TryCatchHelper
    {
        private MethodInfo _lastMethod;

        public event CatchEventHandler Catch;
        public event FinallyEventHandler Finally;

        /// <summary>
        /// Runs the method specified by methodName on the object specified in obj, by providing
        /// the parameters specified in the parameters. It wraps the method in a try..catch block.
        /// </summary>
        /// <param name="obj">Object on which the method should be executed in a try..catch block</param>
        /// <param name="methodName">Method that should be executed on obj</param>
        /// <param name="parameters">Parameters to be passed to the method</param>
        /// <returns>Return value from the method invocation.</returns>
        public object TryCatch(object obj, string methodName, List<object> parameters)
        {
            return RunMethod(obj, null, methodName, parameters, false);
        }

        /// <summary>
        /// Runs the method specified by methodName on the object specified in obj, by providing
        /// the parameters specified in the parameters. It wraps the method in a try..catch..finally block.
        /// </summary>
        /// <param name="obj">Object on which the method should be executed in a try..catch..finally block</param>
        /// <param name="methodName">Method that should be executed on obj</param>
        /// <param name="parameters">Parameters to be passed to the method</param>
        /// <returns>Return value from the method invocation.</returns>
        public object TryCatchFinally(object obj, string methodName, List<object> parameters)
        {
            return RunMethod(obj, null, methodName, parameters, true);
        }

        /// <summary>
        /// Runs the method specified by methodName on the object specified in obj, by providing
        /// the parameters specified in the parameters. It wraps the method in a try..finally block.
        /// </summary>
        /// <param name="obj">Object on which the method should be executed in a try..finally block</param>
        /// <param name="methodName">Method that should be executed on obj</param>
        /// <param name="parameters">Parameters to be passed to the method</param>
        /// <returns>Return value from the method invocation.</returns>
        public object TryFinally(object obj, string methodName, List<object> parameters)
        {
            try
            {
                return RunMethod(obj, null, methodName, parameters);
            }
            finally
            {
                if (Finally != null)
                    Finally(this, _lastMethod);
            }
        }

        /// <summary>
        /// Runs the static method specified by methodName on the type specified in type, by providing
        /// the parameters specified in the parameters. It wraps the method in a try..catch block.
        /// </summary>
        /// <param name="type">Type on which the static method should be executed in a try..catch block</param>
        /// <param name="methodName">Static method that should be executed on obj</param>
        /// <param name="parameters">Parameters to be passed to the method</param>
        /// <returns>Return value from the method invocation.</returns>
        public object TryCatch(Type type, string methodName, List<object> parameters)
        {
            return RunMethod(null, type, methodName, parameters, false);
        }

        /// <summary>
        /// Runs the static method specified by methodName on the type specified in type, by providing
        /// the parameters specified in the parameters. It wraps the method in a try..catch..finally block.
        /// </summary>
        /// <param name="type">Type on which the static method should be executed in a try..catch..finally block</param>
        /// <param name="methodName">Static method that should be executed on obj</param>
        /// <param name="parameters">Parameters to be passed to the method</param>
        /// <returns>Return value from the method invocation.</returns>
        public object TryCatchFinally(Type type, string methodName, List<object> parameters)
        {
            return RunMethod(null, type, methodName, parameters, true);
        }

        /// <summary>
        /// Runs the static method specified by methodName on the type specified in type, by providing
        /// the parameters specified in the parameters. It wraps the method in a try..finally block.
        /// </summary>
        /// <param name="type">Type on which the static method should be executed in a try..finally block</param>
        /// <param name="methodName">Static method that should be executed on obj</param>
        /// <param name="parameters">Parameters to be passed to the method</param>
        /// <returns>Return value from the method invocation.</returns>
        public object TryFinally(Type type, string methodName, List<object> parameters)
        {
            try
            {
                return RunMethod(null, type, methodName, parameters);
            }
            finally
            {
                if (Finally != null)
                    Finally(this, _lastMethod);
            }
        }

        /// <summary>
        /// Wraps a RunMethod method call in a try..catch..finally block. 
        /// </summary>
        /// <param name="obj">Object on which the method is called. Null if static.</param>
        /// <param name="type">Type on which the method is called. Null if instance.</param>
        /// <param name="methodName">Method to be called.</param>
        /// <param name="parameters">Parameters to be passed to the method.</param>
        /// <param name="doFinally">Flag specifying whether the Finally event is to be raised.</param>
        /// <returns>Return value from the method invocation.</returns>
        private object RunMethod(object obj, Type type, string methodName, List<object> parameters, bool doFinally)
        {
            try
            {
                return RunMethod(obj, type, methodName, parameters);
            }
            catch (TargetInvocationException e)
            {
                if (Catch != null)
                    Catch(this, _lastMethod, e.InnerException);
                return null;
            }
            finally
            {
                if (doFinally && Finally != null)
                    Finally(this, _lastMethod);
            }
        }

        /// <summary>
        /// Retrieves a MethodInfo instance for a method from an object or a type based on its name, then invokes the
        /// method with the parameters passed to it, and returns the return value to the caller.
        /// </summary>
        /// <param name="obj">Object on which the method is called. Null if static.</param>
        /// <param name="type">Type on which the method is called. Null if instance.</param>
        /// <param name="methodName">Method to be called.</param>
        /// <param name="parameters">Parameters to be passed to the method.</param>
        /// <returns>Return value from the method invocation.</returns>
        private object RunMethod(object obj, Type type, string methodName, List<object> parameters)
        {
            _lastMethod = GetMethod(obj, type, methodName, parameters);
            return _lastMethod.Invoke(_lastMethod.IsStatic ? null : obj, parameters.ToArray());
        }

        /// <summary>
        /// Retrieves the MethodInfo for a method from an object or a type based on its name. Properly handles generic methods and 
        /// allows the caller to pass a list of parameters that do not directly match the parameter types of the method, but that can
        /// still be called with the parameter list because the parameters can be properly cast to the formal parameter types.
        /// </summary>
        /// <param name="obj">Object on which the method is looked for. Null if static.</param>
        /// <param name="type">Type on which the method is looked for. Null if instance.</param>
        /// <param name="methodName">Method to be found.</param>
        /// <param name="parameters">Parameters to be passed to the method.</param>
        /// <returns>MethodInfo that represents the method to be called.</returns>
        private MethodInfo GetMethod(object obj, Type type, string methodName, List<object> parameters)
        {
            MethodInfo method = (type ?? obj.GetType())
                .GetMethod(methodName, parameters.Select(p => p.GetType()).ToArray()) ??
                          GetMethodSmart(type ?? obj.GetType(), methodName, parameters);
            if (method.IsGenericMethod)
            {
                Type[] types = method.GetGenericArguments();
                for (int i = 0; i < types.Length; i++)
                {
                    if (String.IsNullOrEmpty(types[i].FullName) && (types[i].BaseType == typeof(object)))
                        types[i] = typeof(object);
                }
                method = method.MakeGenericMethod(types);
            }
            return method;
        }

        /// <summary>
        /// Fallback method for the GetMethod method. Looks for a matching method based on assignability of
        /// actual and formal parameter types.
        /// </summary>
        /// <param name="type">Type on which the method is looked for.</param>
        /// <param name="methodName">Method to be found.</param>
        /// <param name="parameters">Parameters to be passed to the method.</param>
        /// <returns>MethodInfo that represents the method to be called.</returns>
        private MethodInfo GetMethodSmart(Type type, string methodName, List<object> parameters)
        {
            foreach (MethodInfo method in type.GetMethods())
            {
                if (method.Name == methodName)
                {
                    bool isThisMethod = false;

                    ParameterInfo[] parameterInfos = method.GetParameters();
                    if (parameterInfos.Length == parameters.Count)
                    {
                        isThisMethod = true;
                        for (int i = 0; i < parameterInfos.Length && isThisMethod; i++)
                        {
                            isThisMethod &=
                                parameters[i].GetType()
                                .IsAssignableFrom(parameterInfos[i].ParameterType) || (parameterInfos[i].ParameterType.IsGenericType && IsAssignableToGenericType(parameters[i].GetType(), parameterInfos[i].ParameterType));
                        }
                    }
                    if (isThisMethod)
                        return method;
                }
            }
            return null;
        }

        /// <summary>
        /// Checks whether a type is assignable to a generic type.
        /// </summary>
        /// <param name="givenType">Type to verify assignability to a generic type.</param>
        /// <param name="genericType">Generic type to which assignability is checked.</param>
        /// <returns>True or False, whether the type is assignable to a generic type.</returns>
        private static bool IsAssignableToGenericType(Type givenType, Type genericType)
        {
            var interfaceTypes = givenType.GetInterfaces();

            if (interfaceTypes.Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType || (it.GUID == genericType.GUID && it.Name == genericType.Name && it.Assembly == genericType.Assembly)))
            {
                return true;
            }

            if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
                return true;

            Type baseType = givenType.BaseType;
            if (baseType == null) return false;

            return IsAssignableToGenericType(baseType, genericType);
        }
    }
}

Okay, I must admit that the class is not nearly as simple as I suggested at the beginning, because getting a MethodInfo from an object is not all that simple as it may seem at first. You must take care of whether methods are static or instance, whether they are generic or if any of their parameters are generic, and also – the catchiest part: you must make sure that the proper method info is detected based on assignability of actual to formal parameters (there is inheritance, interfaces, and explicit typecasting, all of which work with method calls, but actual and formal parameter types may vary). The class is documented, so you can try to figure it out on your own, I’ll rest my case for today.

Please let me know how you like this solution, and please abuse it to your liking.

2 thoughts on “Try..Catch for .NET Interoperability”

  1. Thanks for sharing this Vjeko.

    My feedback would be that, with C/AL now being converted to C# anyway, it would be worth the effort if Microsoft implemented Try, Catch, Finally in C/AL. After all, it is “just” something that the converter should understand.

    We are very close to have C/AL.NET anyway, this would be a major step.

  2. Yes, it would be perfect, but then we have the same considerations that I talked about in http://vjeko.com/blog/try-catch-in-cal

    Transaction simplicity in C/AL is one of its strong points, and it would have to go if we would get 100% C# in place of C/AL, so I think it’s going to be a long way before we get anywhere close to having full TRY..CATCH in C/AL. However, what would really solve a lot of problems, and make tricks such as this simpler, would be if we got full support for delegates in C/AL.

Leave a Reply