簡體   English   中英

Linq.Expression TryCatch - 將異常傳遞給 Catch 塊?

[英]Linq.Expression TryCatch - Pass exception to Catch Block?

所以我一直在修改 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應該返回簡單的計算。 然而,它接受類型對象的參數,這顯然使它在運行時容易出現問題,例如:

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.CatchExpression.Catch定義了我們想要捕獲的異常類型為 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 塊中,您實際上可以使用異常對象。 例如

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

我可以在拋出異常時執行代碼,但我似乎無法弄清楚如何像在普通的 Try...Catch 塊中那樣實際處理異常對象。

任何幫助將不勝感激!

ps 我知道你實際上不會按照我上面所做的方式組織任何事情,但我認為出於示例目的,有必要展示我想要做的事情的機制。

您需要將CatchBlock異常作為參數傳遞給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 )
                ));
        }

這兩種方法都以“我們已經有一個表達式來表示我們將要調用的Action<TInstance>的前提開始。現在我們想要在該 Action 周圍添加一個 try-catch。”

如果沒有 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);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM