【问题标题】:How to assign a value from MemberExpression to a field according to another MemberExpression?如何根据另一个 MemberExpression 将 MemberExpression 中的值分配给字段?
【发布时间】:2015-09-03 10:29:04
【问题描述】:

我想编写一个接受两个 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 的表达式。

此时,我不介意空值或字段的初始化。请假设所有字段都已初始化。

【问题讨论】:

  • 好吧,你已经得到了注释掉的代码......当你尝试这样做时会发生什么,但是使用targetFieldsourceField 而不是xy

标签: c# .net lambda expression expression-trees


【解决方案1】:

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) =&gt; p.NestedField.Dummy - (p) 是该函数的参数。体内的表达式使用它 - p.NestedField.Dummy,所以为了能够编译它 - lambda 表达式需要知道那个参数。

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

【讨论】:

  • 非常感谢!你能详细说明X.Parameters[] 吗?试图用谷歌搜索它,但是关于它的信息太少了=\
【解决方案2】:

这样就可以了:

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 表达式将值分配给属性,只需要调用它。

请注意&lt;TSource, object&gt;,您可能需要额外的演员表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-06
    相关资源
    最近更新 更多