这不是一个简单的问题;你需要重写所有的表达式——严格来说你可以重复利用其中的大部分,但问题在于每个表达式中都有不同的x
(尽管看起来相同),因此你需要使用访问器将所有参数替换为最终的x
。幸运的是,在4.0版本中这并不太难。
static void Main() {
Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };
var convert = Combine(selector1, selector2, selector3, selector4);
var orig = new Agency
{
Name = "a",
PhoneNumber = "b",
Locality = new Location { Name = "c" },
Employees = new List<Employee> { new Employee(), new Employee() }
};
var dto = new[] { orig }.AsQueryable().Select(convert).Single();
Console.WriteLine(dto.Name);
Console.WriteLine(dto.Phone);
Console.WriteLine(dto.Location);
Console.WriteLine(dto.EmployeeCount);
}
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
params Expression<Func<TSource, TDestination>>[] selectors)
{
var zeroth = ((MemberInitExpression)selectors[0].Body);
var param = selectors[0].Parameters[0];
List<MemberBinding> bindings = new List<MemberBinding>(zeroth.Bindings.OfType<MemberAssignment>());
for (int i = 1; i < selectors.Length; i++)
{
var memberInit = (MemberInitExpression)selectors[i].Body;
var replace = new ParameterReplaceVisitor(selectors[i].Parameters[0], param);
foreach (var binding in memberInit.Bindings.OfType<MemberAssignment>())
{
bindings.Add(Expression.Bind(binding.Member,
replace.VisitAndConvert(binding.Expression, "Combine")));
}
}
return Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(zeroth.NewExpression, bindings), param);
}
class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression from, to;
public ParameterReplaceVisitor(ParameterExpression from, ParameterExpression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : base.VisitParameter(node);
}
}
这个代码使用了找到的第一个表达式的构造函数,因此您可能希望检查所有其他的在其相应的NewExpression
中是否使用了平凡的构造函数。但我把这留给读者来完成。
编辑:在评论中,@Slaks指出更多的LINQ可以使代码更短。当然他是对的-不过可能有点密集以至于不易阅读:
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
params Expression<Func<TSource, TDestination>>[] selectors)
{
var param = Expression.Parameter(typeof(TSource), "x");
return Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(
Expression.New(typeof(TDestination).GetConstructor(Type.EmptyTypes)),
from selector in selectors
let replace = new ParameterReplaceVisitor(
selector.Parameters[0], param)
from binding in ((MemberInitExpression)selector.Body).Bindings
.OfType<MemberAssignment>()
select Expression.Bind(binding.Member,
replace.VisitAndConvert(binding.Expression, "Combine")))
, param);
}