Jochen Kühner
Jochen Kühner

Reputation: 1411

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?

Upvotes: 4

Views: 1563

Answers (1)

Jochen K&#252;hner
Jochen K&#252;hner

Reputation: 1411

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);
            }
        }
    }
}

Upvotes: 1

Related Questions