Мы начнем с того, с какого выражения мы имеем дело.
Если мы проверим expression.Body.GetType()
, мы увидим, что это MemberInitExpression
. Он состоит из NewExpression
, который является битом new Emp
, и числом Bindings
, которые являются инициализаторами членов.
Мы ' мы собираемся использовать ExpressionVisitor
, чтобы переписать это, поскольку это немного чище, чем деконструкция / реконструкция его сами.
Мы также собираемся следовать документам для Expression.MakeMemberInit
, который описывает, как позвонить Expression.Bind
.
Соберите все вместе, и мы получим следующее:
public static class Program
{
public static void Main()
{
Expression<Func<EmpFull, Emp>> expression = t => new Emp
{
Id = t.Id,
Name = t.Name
};
// Additional bindings to add to the MemberInit
var additionalBindings = new[]
{
Expression.Bind(
// Property to assign to
typeof(Emp).GetMember("Address")[0],
// Value to assign to it. We need to access the "Address" member
// of 't' in the 'expression' variable above, which we'll
// get from grabbing 'expression.Parameters[0]'
Expression.MakeMemberAccess(
expression.Parameters[0],
typeof(EmpFull).GetMember("Address")[0])),
};
var visitor = new AddNewBindingsVisitor(additionalBindings);
var rewritten = visitor.Visit(expression);
}
}
public class AddNewBindingsVisitor : ExpressionVisitor
{
private readonly IEnumerable<MemberBinding> bindings;
public AddNewBindingsVisitor(IEnumerable<MemberBinding> bindings) =>
this.bindings = bindings;
protected override Expression VisitMemberInit(MemberInitExpression node) =>
node.Update(node.NewExpression, node.Bindings.Concat(bindings));
}
Это довольно уродливо, но мы можем это улучшить. Мы создадим метод, который принимает два лямбда-выражения, которые принимают MemberInitExpressions
, и мы вернем что-то, что содержит комбинацию всех этих инициализаторов.
public static class Program
{
public static void Main()
{
Expression<Func<EmpFull, Emp>> expression = t => new Emp
{
Id = t.Id,
Name = t.Name
};
Expression<Func<EmpFull, Emp>> additional = t => new Emp
{
Address = t.Address
};
var result = CombineInitializers(expression, additional);
}
private static Expression<Func<TSource, TNew>> CombineInitializers<TSource, TNew>
(Expression<Func<TSource, TNew>> first, Expression<Func<TSource, TNew>> second)
{
if (!(first.Body is MemberInitExpression firstInit))
throw new ArgumentException("Must contain a MemberInitExpression", nameof(first));
if (!(second.Body is MemberInitExpression secondInit))
throw new ArgumentException("Must contain a MemberInitExpression", nameof(second));
var secondUsingFirstParameter = (MemberInitExpression)new ParameterReplaceVisitor(second.Parameters[0], first.Parameters[0]).Visit(secondInit);
var combined = new AddNewBindingsVisitor(secondUsingFirstParameter.Bindings).Visit(firstInit);
return first.Update(combined, first.Parameters);
}
}
public class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression target;
private readonly ParameterExpression replacement;
public ParameterReplaceVisitor(ParameterExpression target, ParameterExpression replacement) =>
(this.target, this.replacement) = (target, replacement);
protected override Expression VisitParameter(ParameterExpression node) =>
node == this.target ? this.replacement : base.VisitParameter(node);
}
public class AddNewBindingsVisitor : ExpressionVisitor
{
private readonly IEnumerable<MemberBinding> bindings;
public AddNewBindingsVisitor(IEnumerable<MemberBinding> bindings) =>
this.bindings = bindings;
protected override Expression VisitMemberInit(MemberInitExpression node) =>
node.Update(node.NewExpression, node.Bindings.Concat(bindings));
}
Это довольно прямое расширение нашего предыдущее использование AddNewBindingsVisitor
. Суть в том, что first
и second
имеют разные объекты параметров (то есть t
), поэтому сначала нам нужно переписать один из них (second
), чтобы использовать тот же параметр, что и другой (first
) ). Мы делаем это с помощью другого посетителя, который просто заменяет один ParameterExpression
другим.