This project has moved and is read-only. For the latest updates, please go here.

DynamicObject relations in v.2.0

Apr 23, 2014 at 4:24 PM
Hello,
I think there is a problem with the generation of the parsed expression in version 2.0 when it comes to boxed types (most notably in DynamicObject instances). For example, the following expression works:
((decimal)e.AmountToWithdraw) > 10m
while the following does not:
e.AmountToWithdraw > 10m
where e is DynamicObject descendant, AmountToWithdraw is a dynamic property (always boxed by the DynamicObject implementation). The later throws the following error:
System.InvalidOperationException: The binary operator GreaterThan is not defined for the types 'System.Object' and 'System.Decimal'.
at System.Linq.Expressions.Expression.GetComparisonOperator(ExpressionType binaryType, String opName, Expression left, Expression right, Boolean liftToNull)
at System.Linq.Expressions.Expression.GreaterThan(Expression left, Expression right, Boolean liftToNull, MethodInfo method)
at ExpressionEvaluator.ExpressionHelper.GetBinaryOperator(Expression le, Expression re, ExpressionType expressionType)
at ExpressionEvaluator.ExpressionHelper.BinaryOperator(Expression le, Expression re, ExpressionType expressionType)
at ExpressionEvaluator.ExprEvalParser.relational_expression()
at ExpressionEvaluator.ExprEvalParser.equality_expression()
at ExpressionEvaluator.ExprEvalParser.and_expression()
at ExpressionEvaluator.ExprEvalParser.exclusive_or_expression()
at ExpressionEvaluator.ExprEvalParser.inclusive_or_expression()
at ExpressionEvaluator.ExprEvalParser.conditional_and_expression()
at ExpressionEvaluator.ExprEvalParser.conditional_or_expression()
at ExpressionEvaluator.ExprEvalParser.null_coalescing_expression()
at ExpressionEvaluator.ExprEvalParser.conditional_expression()
at ExpressionEvaluator.ExprEvalParser.non_assignment_expression()
at ExpressionEvaluator.ExprEvalParser.expression()
at ExpressionEvaluator.AntlrParser.Parse(Expression scope, Boolean isCall)
at ExpressionEvaluator.ExpressionCompiler.BuildTree(Expression scopeParam, Boolean isCall)
at ExpressionEvaluator.CompiledExpression.Compile()

It is evident that the created expression is not explicitly unboxed before calling GreaterThan().
N.B. The problem appears only if the DynamicObject travels over the wire (WCF) and is irrelevant to the underlying implementation. Locally, in the same assembly/test case, everything works fine. I guess that has to do more with the garbage collection than to the actual types created.

Some additional notes:
Apr 23, 2014 at 5:43 PM
I'm proposing the following patch to fix this behavior (also trimming the expression tree a little during the process).

Instead of line 260 of ExpressionHelper.cs (public static Expression GetProperty(Expression le, string membername)) if (isDynamic):
                Expression result = Expression.Dynamic(binder, typeof(object), instance);

                // try to get the property explicitly, get its value and unbox it
                var callSite = CallSite<Func<CallSite, object, object>>.Create(binder);
                var parentObject = Expression.Lambda<Func<object>>(instance).Compile()();
                var propertyValue = callSite.Target(callSite, parentObject);
                if (propertyValue != null && propertyValue.GetType() != typeof(object))
                {
                    // unbox!
                    result = Expression.Constant(propertyValue);
                }
Apr 23, 2014 at 11:36 PM
Thanks, I will take a look at it.
Apr 23, 2014 at 11:40 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Apr 26, 2014 at 2:07 AM
Hi all,

Please be advised I am doing some refactoring, mostly just moving some stuff around into folders. I hope you don't have any changes that will be affected. I am also getting rid of the 1.x legacy code that I was keeping around in the AntlrParser branch for reference.

ExpressionEvaluator - Where the external facing API stuff stays
ExpressionEvaluator.Parser - Where most of the core stuff will go
ExpressionEvaluator.Parser..Expressions - Where container classes that will be used to encapsulate complex expression types will go

I also changed some classes to internal.
Apr 26, 2014 at 3:16 AM
Edited Apr 26, 2014 at 3:28 AM
As you said, the problem does not occur with a local object. Can you provide some sort of test case or describe how it's setup? It would be nice to know how .NET compiles the same expression when remoting is involved. Although ideally I really don't think there should be any evaluation until the whole expression tree has been built.

Furthermore, it seems the code does not work well yet with ScopeCompile.
Sep 10, 2014 at 12:14 PM
Edited Sep 10, 2014 at 12:17 PM
Hello again,

The problem was in our internal type serialization. I do agree that expression evaluation should happen only once and not during parsing. I am proposing these fixes, however, as they do seem to aleviate some of the problems with DynamicObjects:

1) consider the following scenario:
public struct Placeholder { public DynamicDictionary x; }

var p = new Placeholder();
var x = new DynamicDictionary(); // from http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject(v=vs.110).aspx
p.x = x;
x["gotcha"] = DateTime.MinValue;
var c = new CompiledExpression("x.gotcha == DateTime.MinValue");
var typeRegistry = new TypeRegistry();
typeRegistry.RegisterType<DateTime>();
c.TypeRegistry = typeRegistry;
var expr = c.ScopeCompile<Placeholder>();
expr(p);
The code above fails because it tries to compare object to DateTime. I am proposing the following fix (TypeConversion.cs, line 16):
        internal static void Convert(ref Expression le, ref Expression re)
        {
            if (Instance._typePrecedence.ContainsKey(le.Type) && Instance._typePrecedence.ContainsKey(re.Type))
            {
                if (Instance._typePrecedence[le.Type] > Instance._typePrecedence[re.Type]) re = Expression.Convert(re, le.Type);
                if (Instance._typePrecedence[le.Type] < Instance._typePrecedence[re.Type]) le = Expression.Convert(le, re.Type);
            } else if (le.Type == typeof(object) && re.Type != typeof(object))
            {
                le = Expression.Convert(le, re.Type);
            } else if (le.Type != typeof(object) && re.Type == typeof(object))
            {
                re = Expression.Convert(re, le.Type);
            }
        }
Note that the following bug has not been fixed:
x["gotcha"] = 2l;
expression: "1 + x.gotcha > 2"
2) Property resolution may be sped up -- compile-time resolved properties of dynamic objects get casted to object every time, and a penalty is incurred when accessing them. To fix this, ExpressionHelper.GetProperty's if (isDynamic).../else... has to be fixed like this:
            // try resolve the property as a static one first
            var propertyInfo = type.GetProperty(membername);
            if (propertyInfo != null)
            {
                return Expression.Property(instance, propertyInfo);
            }
            else
            {
                var fieldInfo = type.GetField(membername);
                if (fieldInfo != null)
                {
                    return Expression.Field(instance, fieldInfo);
                }
            }

            if (isDynamic)
            {
                var binder = Binder.GetMember(
                    CSharpBinderFlags.None,
                    membername,
                    type,
                    new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }
                    );

                Expression result = Expression.Dynamic(binder, typeof(object), instance);

                return result;
            }

            throw new Exception();
Last, but not least, it may be useful to compile expressions with return types known in advance (or forced). Therefore, I am proposing the following overload of CompiledExpression.ScopeCompile():
        public Func<TParam, TResult> ScopeCompile<TParam, TResult>()
        {
            var scopeParam = Expression.Parameter(typeof(TParam), "scope");
            if (Expression == null)
            {
                Expression = Expression.Convert(BuildTree(scopeParam), typeof(TResult));
            }
            return Expression.Lambda<Func<TParam, TResult>>(Expression, new ParameterExpression[] { scopeParam }).Compile();
        }