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

Linq.Expression TryCatch - Pass exception to Catch Block?

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

假设我们有一个简单的数学运算 class:

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应该return简单的计算。但是,它接受 Type Object 的参数,这显然会在 运行 时出现问题,例如:

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 采用表达式主体,然后是 CatchBlock 处理程序的集合。 returnMethodWithParametersAsObject是我们要包裹的表达式,Expression.Catch定义了我们要捕获的Exception是InvalidCastException类型,它的Expression body是一个常量,55。

所以异常被处理了,但是它没有多大用处,除非我想在抛出异常时总是return一个静态值。所以 returning 到 SimpleMath class 我添加了一个新方法 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 块中,您实际上可以随意使用异常对象。例如

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

我可以在抛出异常时执行代码,但我似乎无法弄清楚如何像在正常的 Try...Catch 块中那样实际处理异常对象。

如有任何帮助,我们将不胜感激!

p.s。我知道您实际上不会按照我上面所做的方式组织任何事情,但我认为出于示例目的有必要展示我想做的事情的机制。

您需要将捕获的异常作为参数传递给 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
    

@GeorgeAlexandria 的回答完全正确。但是当我来做同样的事情时,我发现很难全部解码。

也许下面的代码(编写为 2 个辅助方法)将帮助下一个需要做这样的事情的人...

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

这两种方法都是从一个前提出发的"we already have an Expression representing an Action<TInstance> that we are about to invoke. And now we want to add a try-catch around that Action."

如果没有 Try-Catch,我们会完成:

Action finalLamda = Expression.Lambda<Action<TInstance>>(actionExpression, instanceExpression).Compile();

现在我们做:

var actionWithTryCatchExpression = WrapActionExpressionIn_Try_Catch_RethrowWithAdditionalMessage(actionExpression);
Action finalLamda = Expression.Lambda<Action<TInstance>>(actionWithTryCatchExpression, instanceExpression).Compile();

两位帮手分别代表:

try
{
    action();
}
catch(Exception)
{
    throw new Exception(newMessage);
}

\and

try
{
    action();
}
catch(Exception e)
{
    throw new Exception(additionalMessage, e);
}