Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq.Expression TryCatch - Pass exception to Catch Block?

So I've been tinkering with Linq.Expressions (and if anyone can suggest a more proper or more elegant way to do what I'm doing please feel free to chime in) and have hit a wall in trying to do something.

Let's imagine we have a simple math class:

public class SimpleMath {
    public int AddNumbers(int number1, int number2) {           
        return number1 + number2;
    }
}

I decide I want to convert our AddNumbers method to a simple Func<object, object, object> delegate.

To do this I do the following:

// Two collections, one for Type Object paramaters and one for converting to Type int.
List<ParameterExpression> parameters = new List<ParameterExpression>();
List<Expression> convertedParameters = new List<Expression>();

// Populate collections with Parameter and conversion
ParameterExpression parameter1 = Expression.Parameter(typeof(object));
parameters.Add(parameter1);
convertedParameters.Add(Expression.Convert(parameter1, typeof(int)));

ParameterExpression parameter2 = Expression.Parameter(typeof(object));
parameters.Add(parameter2);
convertedParameters.Add(Expression.Convert(parameter2, typeof(int)));

// Create instance of SimpleMath
SimpleMath simpleMath = new SimpleMath();

// Get the MethodInfo for the AddNumbers method
MethodInfo addNumebrsMethodInfo = simpleMath.GetType().GetMethods().Where(x => x.Name == "AddNumbers").ToArray()[0];
// Create MethodCallExpression using the SimpleMath object, the MethodInfo of the method we want and the converted parameters
MethodCallExpression returnMethodWithParameters = Expression.Call(Expression.Constant(simpleMath), addNumebrsMethodInfo, convertedParameters);

// Convert the MethodCallExpression to return an Object rather than int
UnaryExpression returnMethodWithParametersAsObject = Expression.Convert(returnMethodWithParameters, typeof(object));

// Create the Func<object, object, object> with our converted Expression and Parameters of Type Object
Func<object, object, object> func = Expression.Lambda<Func<object, object, object>>(returnMethodWithParametersAsObject, parameters).Compile();
    object result = func(20, 40); // result = 60

So if you run that code func should return simple calculations. However, it accepts parameters of Type Object, which obviously leaves it open to problems at runtime, for example:

object result1 = func(20, "f"); // Throws InvalidCastException

So I want to wrap the method in a Try...Catch (obviously this exact problem would be picked up at compile time if we were dealing with a straight call to AddNumbers and passing a string as a parameter).

So to catch this exception I can do the following:

TryExpression tryCatchMethod = TryExpression.TryCatch(returnMethodWithParametersAsObject, Expression.Catch(typeof(InvalidCastException), Expression.Constant(55, typeof(object))));
Func<object, object, object> func = Expression.Lambda<Func<object, object, object>>(tryCatchMethod, parameters).Compile();
object result = func(20, "f"); // result = 55

The TryExpression.TryCatch takes an Expression body and then a collection of CatchBlock handlers. returnMethodWithParametersAsObject is the expression we wish to wrap, Expression.Catch defines that the Exception we want to catch is of Type InvalidCastException and its Expression body is a constant, 55.

So the exception is handled, but it's not much use unless I want to always return a static value when the exception is thrown. So returning to the SimpleMath class I add a new method HandleException:

public class SimpleMath {
    public int AddNumbers(int number1, int number2) {
        return number1 + number2;
    }

    public int HandleException() {
        return 100;
    }
}

And following the same process above I convert the new method to an Expression:

MethodInfo handleExceptionMethodInfo = simpleMath.GetType().GetMethods().Where(x => x.Name == "HandleException").ToArray()[0];
MethodCallExpression returnMethodWithParameters2 = Expression.Call(Expression.Constant(simpleMath), handleExceptionMethodInfo);
UnaryExpression returnMethodWithParametersAsObject2 = Expression.Convert(returnMethodWithParameters2, typeof(object));

Then using it when creating the TryCatch block:

TryExpression tryCatchMethod2 = TryExpression.TryCatch(returnMethodWithParametersAsObject, Expression.Catch(typeof(InvalidCastException), returnMethodWithParametersAsObject2));
Func<object, object, object> func = Expression.Lambda<Func<object, object, object>>(tryCatchMethod2, parameters).Compile();
object result = func(20, "f"); // result = 100

So this time when the InvalidCastException is thrown the SimpleMath.HandleException method will be executed. So far so good, I can now execute some code when there is an exception.

My problem now is, in a normal inline Try...Catch block you actually have the exception object at your disposal. E.g.

try {
    // Do stuff that causes an exception
} catch (InvalidCastException ex) {
    // Do stuff with InvalidCastException ex
}

I can execute code when an exception is thrown but I can't seem to figure out how to actually get my hands on the exception object like you would in a normal Try...Catch block.

Any help would be appreciated!

p.s. I know that you wouldn't actually organise anything in the way I've done above but I thought it was necessary for example purposes to show the mechanics of what I want to do.

like image 463
David Avatar asked Aug 31 '17 13:08

David


1 Answers

You need to pass your catched exception to CatchBlock expression as parameter. For this you should do:

  • Change signature of HandleException. It will takes a exception as argument:

    public int HandleException(InvalidCastException exp)
    {
        // Put here some real logic. I tested it using line below
        Console.WriteLine(exp.Message);
        return 100;
    }
    
  • Use CatchBlock.Variable to pass your handled exception into catch block. You can set it use constructor. Read comment in the code below:

        // Create parameter that will be passed to catch block 
        var excepParam = Expression.Parameter(typeof(InvalidCastException));
    
        MethodInfo handleExceptionMethodInfo = simpleMath.GetType().GetMethods().Where(x => x.Name == "HandleException").ToArray()[0];
        MethodCallExpression returnMethodWithParameters2 = Expression.Call(Expression.Constant(simpleMath), handleExceptionMethodInfo, excepParam);
        UnaryExpression returnMethodWithParametersAsObject2 = Expression.Convert(returnMethodWithParameters2, typeof(object));
    
        // Put created parameter before to CatchBlock.Variable using Expression.Catch 
        // that takes the first argument as ParameterExpression
        TryExpression tryCatchMethod2 = TryExpression.TryCatch(returnMethodWithParametersAsObject, Expression.Catch(excepParam, returnMethodWithParametersAsObject2));
        var exppp = Expression.Lambda<Func<object, object, object>>(tryCatchMethod2, parameters);
        Func<object, object, object> func2 = Expression.Lambda<Func<object, object, object>>(tryCatchMethod2, parameters).Compile();
        object result2 = func2(20, "f"); // result = 100
    
like image 90
George Alexandria Avatar answered Oct 24 '22 06:10

George Alexandria