Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expression.Convert within Expression.TryCatch

I am trying to add some error-handling to a block of code in the Effort library which generates an expression tree to perform a conversion and assign that result to a property.

The problem with the existing code is that a NullReferenceException is thrown when this expression is invoked at runtime when trying to assign null to a property with a value type. In this case I have no information about the property it was attempting to assign, so I want to throw a more specific exception.

Following is my first attempt to just encapsulate this logic in a try/catch block, and throw an exception if the conversion fails. Eventually I would add more information to the InvalidOperationException.

blockElements.Add(
    Expression.TryCatch(
        Expression.Assign(
            Expression.Property(result, this.Properties[i]),
            Expression.Convert(
                Expression.ArrayIndex(parameter, Expression.Constant(i)),
                this.Properties[i].PropertyType)),
        Expression.Catch(typeof(NullReferenceException),
            Expression.Throw(Expression.Constant(
            new InvalidOperationException("Unhandled exception"))))));

In my mind this is what I'm trying to do:

try
{
    Property = (int)value;
}
catch (NullReferenceException)
{
    throw new InvalidOperationException("Unhandled exception");
}

However at runtime, that expression now throws an ArgumentException with the message "Body of catch must have the same type as body of try." What am I doing wrong here? Do I need to create a Block in the Catch expression to "return" some dummy value, even though it will never be hit due to the Throw?

Or am I approaching this in entirely the wrong way?

like image 219
Tobias J Avatar asked Jan 16 '13 18:01

Tobias J


1 Answers

In normal C# code, a method as a whole has to either return a value or throw an exception.

With Expressions, it works a bit different: each expression has a return type, and in the case of TryCatch, the return type of the try Expression has to be the same as the return type of any of the catch Expressions.

In your case, the type of the try is int, but the type of the catch is void, so they can't be used together. To fix this, you need to either change the type of the try to void, or change the type of the catch to int.

To change the type of the try to void, you can use an overload of Expression.Block() that lets you specify the type of the block (normally, it's the same as the type of the last expression in the block):

Expression.TryCatch(
    Expression.Block(
        typeof(void),
        Expression.Assign(…)),
    Expression.Catch(
        typeof(NullReferenceException),
        Expression.Throw(
            Expression.Constant(
                new InvalidOperationException("Unhandled exception")))))

To change the type of the catch to int, you would somehow need to change the type of the Throw expression. And since for a Throw expression, any return type can be valid (because it doesn't actually return), there is an overload that lets you specify the return type:

Expression.TryCatch(
    Expression.Assign(…),
    Expression.Catch(
        typeof(NullReferenceException),
        Expression.Throw(
            Expression.Constant(
                new InvalidOperationException("Unhandled exception")),
            typeof(int))))

I think changing the type of the try is conceptually clearer, because you don't actually want to return anything from the whole expression.

like image 180
svick Avatar answered Nov 16 '22 02:11

svick