如何根据另一个 MemberExpression 将 MemberExpression 的值分配给字段?
How to assign a value from MemberExpression to a field according to another 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
的表达式。
此时,我不介意空值或字段初始化。请假设所有字段都已初始化。
这样做:
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 的 属性 和 returns 一个动作,它根据第二个 lambda 表达式将值赋给 属性 lambda表达式,只需要调用即可。
尽管如此,请注意 <TSource, object>
,您可能需要额外的转换。
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
class 完成的,但在你的情况下,我们可以在不改变树体的情况下重新发送原始表达式。
我想编写一个接受两个 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
的表达式。
此时,我不介意空值或字段初始化。请假设所有字段都已初始化。
这样做:
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 的 属性 和 returns 一个动作,它根据第二个 lambda 表达式将值赋给 属性 lambda表达式,只需要调用即可。
尽管如此,请注意 <TSource, object>
,您可能需要额外的转换。
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
class 完成的,但在你的情况下,我们可以在不改变树体的情况下重新发送原始表达式。