Why is a conversion necessary in Expression Trees

2019-04-07 06:53发布

问题:

From this question I asked 5 minutes ago, it's clear that the following code throws an exception, stating that

Unhandled Exception: System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Nullable`1[System.Int32]' and 'System.Int32'.

Code

public static void GetResultCollection<T>() {
        AccrualTrackingEntities db = new AccrualTrackingEntities();
        var result = db.CreateQuery<T>(String.Format("[{0}]", typeof(T).Name + "s"));

        int? ItemTypeValue = 1;

        var param = Expression.Parameter(typeof(T));

        var lambda = Expression.Lambda<Func<T, bool>>(
            Expression.Equal(
                Expression.Property(param, "ProcInstId"),
                Expression.Constant(ItemTypeValue)),
            param);

        var list = result.Where(lambda).ToList();
    }

This code, however, with the type explicitly listed in Expression.Constant does work

class Program {
    public static void GetResultCollection<T>() {
        AccrualTrackingEntities db = new AccrualTrackingEntities();
        var result = db.CreateQuery<T>(String.Format("[{0}]", typeof(T).Name + "s"));

        int? ItemTypeValue = 1;

        var param = Expression.Parameter(typeof(T));

        var lambda = Expression.Lambda<Func<T, bool>>(
            Expression.Equal(
                Expression.Property(param, "ProcInstId"),
                Expression.Constant(ItemTypeValue, typeof(int?))),
            param);

        var list = result.Where(lambda).ToList();
    }

The question is, why is Expression.Constant not able to convert implicitly from int? to ... int?

回答1:

Expression trees work at a lower level to normal source code - you can think of them as working at the level of the output of the compiler rather than the input. So while there's an implicit conversion from int to int? in C#, that conversion has to be represented in IL whenever the compiler uses it for a normal method... so it also has to be present in an expression tree representation.

Having said that, your example is somewhat unclear, given that you're trying to use an int (namely ItemTypeValue.Value) as a value for an int? constant, and we don't know what the type of the ItemType property is either.

A short but complete example of what you'd expect to work would really help.

EDIT: Okay, I think I'm with you now. The problem is that if you use

int? foo = 1;
Expression.Constant(foo);

then that calls Expression.Constant(object) which boxes the value of foo. At that point, Expression.Constant can't tell it was originally an int?, because it's now a boxed int. That's just the way .NET boxing works:

int? foo = 1;
object o = foo;
Console.WriteLine(o.GetType()); // Prints System.Int32

That overload of Expression.Constant determines the overall type of the expression from the value that it's given - so it creates an int expression, whereas you really want an int? expression.

In order to maintain the type information properly, you have to use the overload which allows you to specify the type:

int? foo = 1;
Expression.Constant(foo, typeof(int?));

It's still not entirely clear from your question which code works and which doesn't, but hopefully that'll help...