如何根据另一个MemberExpression将MemberExpression中的值赋给字段



我想编写一个接受两个MemberExpression的方法,并生成一个接受两个对象(源和目标)的委托,并根据源的MemberExpression将值分配给目标的字段,根据第二个MemberExpression。对象不必具有相同的类型。我在找这样的东西:

public Action<TSource, TTarget> Map(Expression<Func<TSource, object>> getter, Expression<Func<TTarget, object>> setter)
{
    var sourceField = getter.Body as MemberExpression;
    var targetField = setter.Body as MemberExpression;
    /*
     * Now I would like to create a lambda expression which accepts TSource and TTarget instances,
     * and assings TTarget according to the above getter and setter expressions. Kind of like:
     * var assignExp = Expression.Assign(x, y);
     * var lambda = Expression.Lambda<Action<TTarget, TSource>>( .... ).Compile();
     * return lambda;
     */
} 

用法:

Target target;
Source source;
//... 
var action = Map(p => p.NestedField.Dummy, x => x.TargetName);
action(source, target);

我不明白如何构建表达式发送到Expression.Assign

在这一点上,我不介意空值或字段的初始化。请假定所有字段都已初始化。

Assign用于生成赋值表达式,但在您的情况下,每个lambda表达式都有自己的参数,并且这两个参数都应该发送给新的lambda表达式。

所以在我的例子中,我生成新的赋值表达式,然后创建一个新的lambda表达式,并将ParameterExpression从getter和setter表达式发送到一个新的lambda。

应该是这样的

下面是工作示例- https://dotnetfiddle.net/uuPVAl和代码本身

using System;
using System.Linq.Expressions;
public class Program
{
    public static void Main(string[] args)
    {
        Target target = new Target();
        Source source = new Source()
        {
            NestedField = new NestedSource()
            {
                Dummy = "Hello world"
            }
        };

        var action = Map<Source, Target>(p => p.NestedField.Dummy, x => x.TargetName);
        action(source, target);
        Console.WriteLine(target.TargetName);
    }
    public static Action<TSource, TTarget> Map<TSource, TTarget>(Expression<Func<TSource, object>> getter, Expression<Func<TTarget, object>> setter)
    {
        var sourceField = getter.Body as MemberExpression;
        var targetField = setter.Body as MemberExpression;
        // here we create new assign expression
        var assign = Expression.Assign(targetField, sourceField);
        // and then compile it with original two parameters
        var lambda = Expression.Lambda<Action<TSource, TTarget>>(assign, getter.Parameters[0], setter.Parameters[0]);
        return lambda.Compile();
    }
}
public class Target
{
    public string TargetName { get; set; }
}
public class NestedSource
{
    public string Dummy { get; set; }
}
public class Source
{
    public NestedSource NestedField { get; set; }
}

所以每个Lambda表达式可以有任何参数。从代码端来看,它是ParameterExpression。当您将表达式作为典型代码编写时,它意味着函数参数,因此在您的示例中(p) => p.NestedField.Dummy - (p)是该函数的参数。并且表达式内部使用它- p.NestedField.Dummy,所以为了能够编译它- lambda表达式需要知道该参数。

在这种情况下,你有两个lambda表达式的目标和源,他们每个都有自己的参数- (p)(x),每个表达式使用自己的参数。但是在结果函数中,我们需要使用它们两个,因为我们在函数中有两个参数,所以我们需要从源和目标重新发送原始ParameterExpression到一个新的lambda。或者你可以创建一个新的ParameterExpression,但是你需要创建一个新的树,因为旧的树将使用旧的ParameterExpression。通常这样的事情是用ExpressionVisitor类完成的,但在您的情况下,我们可以重新发送原始表达式,而不需要更改树体。

这样做:

public Action<TSource, TTarget> Map<TSource, TTarget>(Expression<Func<TSource, object>> getter, Expression<Func<TTarget, object>> setter)
    {
        var targetPropertyExpression = setter.Body as MemberExpression;
        var targetProperty = targetPropertyExpression.Member as PropertyInfo;
        return (src, tgt) => { targetProperty.SetValue(tgt, getter.Compile().Invoke(src)); };
    } 

它从第一个lambda表达式中获取setter的属性,并返回一个操作,该操作根据只需要调用的第二个lambda表达式将值分配给属性。

照顾<TSource, object>虽然,你可能需要一个额外的演员。

相关内容

  • 没有找到相关文章

最新更新