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);
}
所以我一直在修补 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);
}