简体   繁体   中英

How to assign a value from MemberExpression to a field according to another MemberExpression?

I would like write a method which accepts two MemberExpression, and generates a delegate which accepts two objects - source and target, and assigns the value from the source - according to it's MemberExpression, to the field of the target, according to the second MemberExpression. The objects does not have to be of the same type. I'm looking for something like this:

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;
     */

} 

Usage:

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

I don't understand how to build the expressions to send to Expression.Assign .

At this point, I don't mind about null values or initialization of fields. Please assume all fields are initialized.

This will do:

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

It get's the property of the setter from the 1st lambda expression and just returns an action, which assigns the value to the property based on the 2nd lambda expression, which just needs to be invoked.

Take care of the <TSource, object> though, you maybe need an additional cast.

Assign is used to generate assign expression, but in your case each lambda expression has own parameter, and both this parameters should be send to a new lambda expression.

So in my example i generate new assign expression, then create a new lambda expression, and send ParameterExpression from both getter and setter expressions to a new lambda.

So it should be like this:

Here is working sample - https://dotnetfiddle.net/uuPVAl and the code itself

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

UPDATE

So each Lambda Expression can have any parameters. From code side it's ParameterExpression . When you write expression as typical code, then it means function parameters, so in your case (p) => p.NestedField.Dummy - (p) is parameter of that function. And expression inside body uses it - p.NestedField.Dummy , so to be able to compile it - lambda expression needs to know that parameter.

In this case you have two lambda expressions for target and source, and each of them have own parameter - (p) and (x) and each expression use own parameter. But in result function we need to use both of them, as we have two parameters in the function, so we need to resend original ParameterExpression from source and target to a new lambda. Or you can create a new ParameterExpression but then you need to create a new tree as old one will use old ParameterExpression . Usually such things are done with ExpressionVisitor class, but in your case we can just resend original expressions without tree body changes.

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