jeudi 1 octobre 2020

.NET Bind multiple expressions into an anonymous object

I found out a solution, shown below, to create an anonymous object through multiple Expression<Func<T1,T2>>

public class FieldSelector
{
    public static Expression<Func<TSource, TDestination>> GetSelector<TSource, TDestination>(params Expression<Func<TSource, TDestination>>[] selectors)
    {
        // We will create a new type in runtime that looks like a AnonymousType
        var str = $"<>f__AnonymousType0`{selectors.Count()}";

        // Create type builder
        var assemblyName = Assembly.GetCallingAssembly().GetName();
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("module");
        var typeBuilder = moduleBuilder.DefineType(str, TypeAttributes.Public | TypeAttributes.Class);

        var types = new Type[selectors.Count()];
        var names = new List<string>[selectors.Count()];

        for (int i = 0; i < selectors.Count(); i++)
        {
            Expression exp;

            // Retrive passed properties
            exp = !(selectors[i].Body is UnaryExpression unExpr) ?
                        selectors[i].Body as MemberExpression :
                        unExpr.Operand as MemberExpression;

            types[i] = exp.Type;
            // Retrive a nested properties
            names[i] = GetAllNestedMembersName(exp);
        }

        // Defined generic parameters for custom type
        var genericParams = typeBuilder.DefineGenericParameters(types.Select((_, i) => $"PropType{i}").ToArray());

        for (int i = 0; i < types.Length; i++)
        {
            typeBuilder.DefineField($"{string.Join("_", names[i])}", genericParams[i], FieldAttributes.Public);
        }

        // Create generic type by passed properties
        var type = typeBuilder.CreateType();
        var genericType = type.MakeGenericType(types);
        var parameter = Expression.Parameter(typeof(TSource), "item");

        // Create nested properties
        var assignments = genericType
            .GetFields()
            .Select((prop, i) => Expression.Bind(prop, GetAllNestedMembers(parameter, names[i])));

        return Expression.Lambda<Func<TSource, TDestination>>(Expression.MemberInit(Expression.New(genericType.GetConstructors()[0]), assignments), parameter);
    }

    static Expression GetAllNestedMembers(Expression parameter, List<string> properties)
    {
        Expression expression = parameter;

        for (int i = 0; i < properties.Count; ++i)
        {
            expression = Expression.Property(expression, properties[i]);
        }

        return expression;
    }

    static List<string> GetAllNestedMembersName(Expression arg)
    {
        var result = new List<string>();
        var expression = arg as MemberExpression;

        while (expression != null && expression.NodeType != ExpressionType.Parameter)
        {
            result.Insert(0, expression.Member.Name);
            expression = expression.Expression as MemberExpression;
        }

        return result;
    }
}

Problem

The above solution is working pretty well with MemberExpressions like

  • obj => obj.{PropertyName}

But and if I have expressions like the following ones

  • obj => string.Join(',', obj.Values.Select(v => v.Name)) - MethodCallExpression
  • obj => obj.Item != null ? obj.Item.Description : string.Empty - Conditional

Is it possible to bind the kinda expressions as MemberAssignments?

I put just a random example, feel free to test this with your own.

Thanks in advance





Aucun commentaire:

Enregistrer un commentaire