Linq.Expression TryCatch - 将异常传递给Catch Block?

时间:2017-08-31 13:43:07

标签: c# lambda linq-expressions

所以我一直在修补Linq.Expressions(如果有人可以建议一个更正确或更优雅的方式来做我正在做的事情,请随意插入)并且试图做某事时碰壁。

让我们假设我们有一个简单的数学课:

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

我决定将AddNumbers方法转换为简单的Func<object, object, object>委托。

为此,我执行以下操作:

// 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

因此,如果您运行该代码func应该返回简单的计算。但是,它接受Type对象的参数,这显然会使它在运行时出现问题,例如:

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

所以我想将方法​​包装在Try...Catch中(显然,如果我们处理对AddNumbers的直接调用并将字符串作为参数传递,那么在编译时会出现这个问题。 )。

因此,为了捕获此异常,我可以执行以下操作:

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

TryExpression.TryCatch接受一个Expression主体,然后是一个CatchBlock处理程序的集合。 returnMethodWithParametersAsObject是我们希望包装的表达式,Expression.Catch定义我们要捕获的Exception是Type InvalidCastException,它的Expression主体是常量,55。

因此处理了异常,但除非我想在抛出异常时总是返回静态值,否则它没什么用处。因此,返回SimpleMath课程,我添加了一个新方法HandleException

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

    public int HandleException() {
        return 100;
    }
}

按照上面的相同过程,我将新方法转换为表达式:

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));

然后在创建TryCatch块时使用它:

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

因此,当抛出InvalidCastException时,将执行SimpleMath.HandleException方法。到目前为止一切顺利,我现在可以在出现异常时执行一些代码。

我现在的问题是,在正常的内联Try ... Catch块中,你实际上有你可以使用的异常对象。 E.g。

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

我可以在抛出异常时执行代码,但我似乎无法弄清楚如何实际操作异常对象,就像在普通的Try ... Catch块中一样。

任何帮助将不胜感激!

P.S。我知道你实际上并没有按照我上面的方式组织任何事情,但我认为有必要举例说明我想做什么的机制。

2 个答案:

答案 0 :(得分:1)

您需要将捕获的异常作为参数传递给CatchBlock表达式。 为此你应该这样做:

  • 更改HandleException的签名。它将以一个例外作为参数:

    public int HandleException(InvalidCastException exp)
    {
        // Put here some real logic. I tested it using line below
        Console.WriteLine(exp.Message);
        return 100;
    }
    
  • 使用CatchBlock.Variable将处理后的异常传递给catch块。您可以设置它使用构造函数。阅读以下代码中的评论:

        // 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
    

答案 1 :(得分:0)

@GeorgeAlexandria的答案是完全正确的。但是当我做同一件事时,我发现很难解码所有内容。

也许下面的代码(写为2个辅助方法)将帮助下一个需要执行此操作的人...

false

这两个方法都从以下前提开始:“我们已经有了一个表示要调用的false的Expression。现在我们想在该Action周围添加一个try-catch。”

没有Try-Catch,我们会做:

allWithFailFast.complete(false);

现在我们要做:

        private Expression WrapActionExpressionIn_Try_Catch_ThrowNewMessage(Expression coreExpression, string newMessage)
        {
            return
                Expression.TryCatch(
                    coreExpression,
                Expression.Catch(typeof(Exception),
                    Expression.Throw(
                        Expression.Constant(new Exception(newMessage))
                    )
                ));
        }

        private Expression WrapActionExpressionIn_Try_Catch_RethrowWithAdditionalMessage(Expression coreExpression, string additionalMessage)
        {
            var caughtExceptionParameter = Expression.Parameter(typeof(Exception));

            //We want to call `new Exception(additionalMessage, caughtException)`
            var ctorForExceptionWithMessageAndInnerException = typeof(Exception).GetConstructor(new[] {typeof(string), typeof(Exception)});
            var replacementExceptionExpresion = Expression.New(ctorForExceptionWithMessageAndInnerException, Expression.Constant(additionalMessage), caughtExceptionParameter);

            return
                Expression.TryCatch(
                    coreExpression,
                Expression.Catch(caughtExceptionParameter,
                    Expression.Throw( replacementExceptionExpresion )
                ));
        }

这两个助手分别代表:

Action<TInstance>