Search code examples
c#linqexpressionexpressionvisitor

Rewrite of an Expression Tree


I have the following Expression:

.Call System.Linq.Queryable.Select(
    .Constant<System.Linq.EnumerableQuery`1[System.Linq.Dynamic.Tests.Helpers.User]>(System.Linq.Dynamic.Tests.Helpers.User[]),
    '(.Lambda #Lambda1<System.Func`2[System.Linq.Dynamic.Tests.Helpers.User,System.Linq.Dynamic.DynamicObjectClass]>))

.Lambda #Lambda1<System.Func`2[System.Linq.Dynamic.Tests.Helpers.User,System.Linq.Dynamic.DynamicObjectClass]>(System.Linq.Dynamic.Tests.Helpers.User $var1)
{
    .New System.Linq.Dynamic.DynamicObjectClass(
        .New System.Collections.Generic.KeyValuePair`2[System.String, System.Object](
            "UserName",
            (System.Object)$var1.UserName),
        .New System.Collections.Generic.KeyValuePair`2[System.String, System.Object](
            "MyFirstName",
            (System.Object)($var1.Profile).FirstName))
}

and want to rewrite it to the following:

.Call System.Linq.Queryable.Select(
    .Constant<System.Linq.EnumerableQuery`1[System.Linq.Dynamic.Tests.Helpers.User]>(System.Linq.Dynamic.Tests.Helpers.User[]),
    '(.Lambda #Lambda1<System.Func`2[System.Linq.Dynamic.Tests.Helpers.User,DynamicClass1]>))

.Lambda #Lambda1<System.Func`2[System.Linq.Dynamic.Tests.Helpers.User,DynamicClass1]>(System.Linq.Dynamic.Tests.Helpers.User $var1)
{
    .New DynamicClass1()
{
    UserName = $var1.UserName,
        MyFirstName = ($var1.Profile).FirstName
    }
}

I tried with the Expression Visitor and following Code:

        protected override Expression VisitNew(NewExpression node)
        {
            if (node.Type == typeof(DynamicObjectClass))
            {
                var properties = new List<DynamicProperty>(node.Arguments.Count);
                var expressions = new List<Expression>(node.Arguments.Count);
                foreach (NewExpression newEx in node.Arguments)
                {
                    var name = ((ConstantExpression)newEx.Arguments.First()).Value as string;
                    var parameter = ((UnaryExpression)newEx.Arguments.Skip(1).First()).Operand;
                    properties.Add(new DynamicProperty(name, parameter.Type));
                    expressions.Add(parameter);
                }

                Type type = DynamicExpression.CreateClass(properties);

                MemberBinding[] bindings = new MemberBinding[properties.Count];
                for (int i = 0; i < bindings.Length; i++)
                    bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]);
                return Expression.MemberInit(Expression.New(type), bindings);
            }
            return base.VisitNew(node);
        }

but I got this Exception:

A first chance exception of type 'System.ArgumentException' occurred in System.Core.dll

Additional Informations: Expression of type 'DynamicClass1' cannot be used for return type 'System.Linq.Dynamic.DynamicObjectClass'

I think I need to overwrite VisitLambda, but I have no exact Idea! Can anyone help me out?


Solution

  • With this Code it now works at the moment (but I don't think it works for all cases...)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    
    namespace System.Linq.Dynamic
    {
        public static class ExpressionConverter
        {
            private static ExpressionConverterVisitor visitor = new ExpressionConverterVisitor();
    
            public static Expression DynamicObjectClassToAnonymousType(this Expression expression)
            {
                return visitor.Visit(expression);
            }
    
            private class ExpressionConverterVisitor : ExpressionVisitor
            {
                protected override Expression VisitLambda<T>(Expression<T> node)
                {
                    if (node.Body is NewExpression && ((NewExpression)node.Body).Type == typeof(DynamicObjectClass))
                    {
                        var e = node.Body as NewExpression;
    
                        var properties = new List<DynamicProperty>(e.Arguments.Count);
                        var expressions = new List<Expression>(e.Arguments.Count);
                        foreach (NewExpression newEx in e.Arguments)
                        {
                            var name = ((ConstantExpression)newEx.Arguments.First()).Value as string;
                            var parameter = ((UnaryExpression)newEx.Arguments.Skip(1).First()).Operand;
                            properties.Add(new DynamicProperty(name, parameter.Type));
                            expressions.Add(parameter);
                        }
    
                        Type type = DynamicExpression.CreateClass(properties);
    
                        MemberBinding[] bindings = new MemberBinding[properties.Count];
                        for (int i = 0; i < bindings.Length; i++)
                            bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]);
                        var membInit = Expression.MemberInit(Expression.New(type), bindings);
    
                        var typeOfTArgs = typeof(T).GetGenericArguments();
    
                        var funcTType = typeof(Func<,>).MakeGenericType(new[] { typeOfTArgs.First(), type });
                        var mi = typeof(Expression).GetMethods().FirstOrDefault(x => x.Name == "Lambda" && x.ContainsGenericParameters);
                        MethodInfo genericMethod = mi.MakeGenericMethod(new[] { funcTType });
                        var lambda = genericMethod.Invoke(null, new object[] { membInit, node.Parameters.ToArray() }) as Expression;
                        return lambda;
                    }
                    return base.VisitLambda<T>(node);
                }
    
                protected override Expression VisitMethodCall(MethodCallExpression node)
                {
                    if (node.Method.Name == "Select")
                    {
                        var arguments = node.Arguments.ToList();
                        for (int n = 0; n < arguments.Count; n++)
                            arguments[n] = visitor.Visit(arguments[n]);
                        var typeList = arguments.Select(x => x.Type).ToArray();
                        var funcTType = typeof(Func<,>).MakeGenericType(typeList);
    
                        var argsmth = node.Method.GetGenericArguments().ToArray();
                        argsmth[1] = ((LambdaExpression)((UnaryExpression)arguments[1]).Operand).Body.Type;
                        var mi = node.Method.DeclaringType.GetMethods().FirstOrDefault(x => x.Name == "Select");
                        var mth = mi.MakeGenericMethod(argsmth);
    
                        return Expression.Call(mth, arguments);
                    }
                    return base.VisitMethodCall(node);
                }
            }
        }
    }