如何将转换函数插入表达式树?

时间:2011-01-13 03:01:24

标签: c# expression-trees

这是表达树的学习练习。

我有这个工作代码:

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

很好地工作。如上所述,它会将相应的属性从f复制到b。现在,假设我想添加一些带有“from property”的转换或格式化方法,做一些魔术,然后设置“to property”等于结果。请注意CreateMapper中间的两条注释掉的行。

我如何做到这一点?我到目前为止,但现在我有点迷失了。

2 个答案:

答案 0 :(得分:3)

您的代码示例几乎就在那里;您可以使用Expression.Call进行转换,因为您正在尝试这样做。您可以分配到表示转换价值的 MethodCallExpression ,而不是将toProp分配给fromProp MemberExpression

这里棘手的部分是找出如何进行转换,我认为这对于不同的属性会有所不同。

您可以将LINQ表达式替换为:

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

(请注意,作业的右侧现在是fromTransformed而不是fromProp。)

其中GetTransform看起来像(我在这里假设转换的性质仅取决于属性的类型):

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

然后剩下要做的唯一事情就是填写转换本身;例如:

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

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

然后,您的使用测试方法将产生:

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

答案 1 :(得分:2)

我对你的代码有点玩,我想我可以给你一个很好的流利风格的字段地图构建器。鉴于您的课程Foo&amp; Bar您可以运行此代码:

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

结果是bar现在看起来好像是这样声明的:

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

关于这段代码的好处是你不需要在调用代码中做任何反射,推断出属性类型,并且它整齐地处理不同类型的映射属性。

以下是执行此操作的代码:

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