简体   繁体   中英

How do I insert a conversion function into my expression tree?

This is a learning exercise in expression trees.

I have this working code:

class Foo
{
    public int A { get; set; }
    public string B { get; set; }
}

class Bar
{
    public int C { get; set;}
    public string D { get; set; }
}

class FieldMap
{
    public PropertyInfo From { get; set; }
    public PropertyInfo To { get; set; }

}

class Program
{
    static Action<TFrom, TTo> CreateMapper<TFrom, TTo>(IEnumerable<FieldMap> fields)
    {
        ParameterExpression fromParm = Expression.Parameter(typeof(TFrom), "from");
        ParameterExpression toParm = Expression.Parameter(typeof(TTo), "to");

        //var test = new Func<string, string>(x => x);
        //var conversionExpression = Expression.Call(null, test.Method);

        var assignments = from fm in fields
                            let fromProp = Expression.Property(fromParm, fm.From)
                            let toProp = Expression.Property(toParm, fm.To)
                            select Expression.Assign(toProp, fromProp);

        var lambda = Expression.Lambda<Action<TFrom, TTo>>(
            Expression.Block(assignments), 
            new ParameterExpression[] { fromParm, toParm });

        return lambda.Compile();
    }

    static void Main(string[] args)
    {
        var pa = typeof(Foo).GetProperty("A");
        var pb = typeof(Foo).GetProperty("B");
        var pc = typeof(Bar).GetProperty("C");
        var pd = typeof(Bar).GetProperty("D");

        var mapper = CreateMapper<Foo, Bar>(new FieldMap[] 
        { 
            new FieldMap() { From = pa, To = pc }, 
            new FieldMap() { From = pb, To = pd } 
        });

        Foo f = new Foo();
        Bar b = new Bar();

        f.A = 20;
        f.B = "jason";
        b.C = 25;
        b.D = "matt";

        mapper(f, b);     // copies properties from f into b
    }
}

Works nicely. As noted it copies the corresponding properties from f to b . Now, supposing I wanted to add some conversion or formatting method that takes the "from property", does some magic, and then sets the "to property" equal to the result. Note the two commented out lines in the middle of CreateMapper .

How do I accomplish this? I got this far, but I'm sort of lost now.

Your code sample is almost there; you can use Expression.Call to do the transformation as you are clearly trying to do. Instead of assigning toProp to the fromProp MemberExpression , you can assign to a MethodCallExpression representing the value of the transformation.

The tricky part here is to figure out how to do the transformation, which I assume will vary for different properties.

You can replace the LINQ expression with:

var assignments = from fm in fields
                  let fromProp = Expression.Property(fromParm, fm.From)
                  let fromPropType = fm.From.PropertyType
                  let fromTransformed 
                       = Expression.Call(GetTransform(fromPropType), fromProp)
                  let toProp = Expression.Property(toParm, fm.To)
                  select Expression.Assign(toProp, fromTransformed);

(Notice that the right-hand side of the assignment is now fromTransformed rather than fromProp .)

where GetTransform looks something like (I've assumed here that the nature of the transformation depends only on the type of the property):

private static MethodInfo GetTransform(Type type)
{
    return typeof(Program).GetMethod(GetTransformName(type));
}

private static string GetTransformName(Type type)
{
    if (type == typeof(int))
        return "MapInt";

    if (type == typeof(string))
        return "MapString";

    throw new ArgumentException("Unknown type");
}

Then the only thing left to do is filling in the transformations themselves; for example:

public static int MapInt(int x)  { return x * 2; }

public static string MapString(string x)  { return x + x;  }

Then, your usage-test method would produce:

b.c == 40
b.d == "jasonjason"

I had a bit of a play with your code and I think I can give you a nice fluent-style field map builder. Given your classes Foo & Bar you could run this code:

var foo = new Foo() { A = 20, B = "jason", };
var bar = new Bar() { C = 25, D = "matt", };

var fm = new FieldMapBuilder<Foo, Bar>()
    .AddMap(f => f.A, b => b.C)
    .AddMap(f => f.B, b => b.D)
    .AddMap(f => f.A, b => b.D, x => String.Format("!{0}!", x))
    .Compile();

fm(foo, bar);

The result is that bar now looks as if it were declared like so:

var bar = new Bar() { C = 20, D = "!20!", };

The nice thing about this code is you don't need to do any reflection in the calling code, property types are inferred, and it neatly handles mapping properties of different types.

Here's the code that does it:

public class FieldMapBuilder<TFrom, TTo>
{
    private Expression<Action<TFrom, TTo>>[] _fieldMaps = null;

    public FieldMapBuilder()
    {
        _fieldMaps = new Expression<Action<TFrom, TTo>>[] { };
    }

    public FieldMapBuilder(Expression<Action<TFrom, TTo>>[] fieldMaps)
    {
        _fieldMaps = fieldMaps;
    }

    public FieldMapBuilder<TFrom, TTo> AddMap<P>(
        Expression<Func<TFrom, P>> source,
        Expression<Func<TTo, P>> destination)
    {
        return this.AddMap<P, P>(source, destination, x => x);
    }

    public FieldMapBuilder<TFrom, TTo> AddMap<PFrom, PTo>(
        Expression<Func<TFrom, PFrom>> source,
        Expression<Func<TTo, PTo>> destination,
        Expression<Func<PFrom, PTo>> map)
    {
        var paramFrom = Expression.Parameter(typeof(TFrom), "from");
        var paramTo = Expression.Parameter(typeof(TTo), "to");

        var invokeExpressionFrom =
                Expression.Invoke(map, Expression.Invoke(source, paramFrom));

        var propertyExpressionTo =
                Expression.Property(paramTo,
            (destination.Body as MemberExpression).Member as PropertyInfo);

        var assignmentExpression =
                Expression.Assign(propertyExpressionTo, invokeExpressionFrom);

        return new FieldMapBuilder<TFrom, TTo>(
                _fieldMaps.Concat(new Expression<Action<TFrom, TTo>>[]
                {
                    Expression.Lambda<Action<TFrom, TTo>>(
                        assignmentExpression,
                        paramFrom,
                        paramTo)
                }).ToArray());
    }

    public Action<TFrom, TTo> Compile()
    {
        var paramFrom = Expression.Parameter(typeof(TFrom), "from");
        var paramTo = Expression.Parameter(typeof(TTo), "to");

        var expressionBlock =
            Expression.Block(_fieldMaps
                .Select(fm => Expression.Invoke(fm, paramFrom, paramTo))
                .ToArray());

        var lambda = Expression.Lambda<Action<TFrom, TTo>>(
            expressionBlock, 
            paramFrom,
            paramTo);

        return lambda.Compile();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM