我有两种类型:Cat
和Dog
。我想用Func<Dog, bool>
选择猫。要做到这一点,我需要一种方法在某种映射器中将属性从Cat映射到Dog(类似于AutoMapper如何将属性从一个对象映射到另一种对象)。
public Cat GetCat(Func<Dog, bool> selector)
{
Func<Cat, bool> mappedSelector = getMappedSelector(selector);
return _catRepository.Get(mappedSelector);
}
private Func<Cat, bool> getMappedSelector(Func<Dog, bool> selector)
{
//some code here to map from one function type to another
//something like AutoMapper would be sweet...
//something that I can configure how I want the properties to be mapped.
}
要么已经有了这样或应该有的东西。
答案 0 :(得分:14)
以下是使用AutoMapper的解决方案:
Func<Cat, bool> GetMappedSelector(Func<Dog, bool> selector)
{
Func<Cat, Dog> mapper = Mapper.CreateMapExpression<Cat, Dog>().Compile();
Func<Cat, bool> mappedSelector = cat => selector(mapper(cat));
return mappedSelector;
}
更新:自从我第一次回答这个问题已经过去了1。5年,我想我现在会扩展我的答案,因为当你有一个表达而不是一个表达时,人们会问这个怎么做委派。
解决方案原则上是相同的 - 我们需要能够compose将两个函数(selector
和mapper
)合并为一个函数。不幸的是,由于C#中没有办法从另一个表达式“调用”一个表达式(就像我们可以使用委托),我们无法在代码中直接表示它。例如,以下代码将无法编译:
Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
{
Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
Expression<Func<Cat, bool>> mappedSelector = cat => selector(mapper(cat));
return mappedSelector;
}
因此,创建我们的组合函数的唯一方法是使用System.Linq.Expressions
类自己构建expression tree。
我们真正需要做的是修改selector
函数的主体,以便其参数的所有实例都被mapper
函数的主体替换。这将成为我们新函数的主体,它将接受mapper
的参数。
要替换参数,我创建了一个ExpressionVisitor类的子类,它可以遍历表达式树并用任意表达式替换单个参数:
class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression _parameter;
private Expression _replacement;
private ParameterReplacer(ParameterExpression parameter, Expression replacement)
{
_parameter = parameter;
_replacement = replacement;
}
public static Expression Replace(Expression expression, ParameterExpression parameter, Expression replacement)
{
return new ParameterReplacer(parameter, replacement).Visit(expression);
}
protected override Expression VisitParameter(ParameterExpression parameter)
{
if (parameter == _parameter)
{
return _replacement;
}
return base.VisitParameter(parameter);
}
}
然后我创建了一个扩展方法Compose()
,它使用访问者组成两个lambda表达式,一个外部和一个内部:
public static class FunctionCompositionExtensions
{
public static Expression<Func<X, Y>> Compose<X, Y, Z>(this Expression<Func<Z, Y>> outer, Expression<Func<X, Z>> inner)
{
return Expression.Lambda<Func<X ,Y>>(
ParameterReplacer.Replace(outer.Body, outer.Parameters[0], inner.Body),
inner.Parameters[0]);
}
}
现在,有了所有基础架构,我们可以修改我们的GetMappedSelector()
方法以使用我们的Compose()
扩展程序:
Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
{
Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
Expression<Func<Cat, bool>> mappedSelector = selector.Compose(mapper);
return mappedSelector;
}
我创建了一个simple console application来测试它。希望我的解释不会太混淆;但不幸的是,没有一种更简单的方法可以做你想做的事情。如果你仍然完全困惑,至少你可以重用我的代码并且对处理表达式树的细微差别和复杂性有所了解!
答案 1 :(得分:2)
@luksan,感谢您的灵感!你的解决方案并没有解决我的问题,但让我思考。由于我需要将已翻译的表达式传递给IQueryable.OrderBy(),因此使用内部表达式转换方法不起作用。但我提出了一个解决方案,可以解决这两种情况,也更容易实现。它也是通用的,因此可以重用于任何映射类型。这是代码:
private Expression<Func<TDestination, TProperty>> GetMappedSelector<TSource, TDestination, TProperty>(Expression<Func<TSource, TProperty>> selector)
{
var map = Mapper.FindTypeMapFor<TSource, TDestination>();
var mInfo = ReflectionHelper.GetMemberInfo(selector);
if (mInfo == null)
{
throw new Exception(string.Format(
"Can't get PropertyMap. \"{0}\" is not a member expression", selector));
}
PropertyMap propmap = map
.GetPropertyMaps()
.SingleOrDefault(m =>
m.SourceMember != null &&
m.SourceMember.MetadataToken == mInfo.MetadataToken);
if (propmap == null)
{
throw new Exception(
string.Format(
"Can't map selector. Could not find a PropertyMap for {0}", selector.GetPropertyName()));
}
var param = Expression.Parameter(typeof(TDestination));
var body = Expression.MakeMemberAccess(param, propmap.DestinationProperty.MemberInfo);
var lambda = Expression.Lambda<Func<TDestination, TProperty>>(body, param);
return lambda;
}
这是ReflectionHelper代码(仅用于使代码保持清洁)
private static class ReflectionHelper
{
public static MemberInfo GetMemberInfo(Expression memberExpression)
{
var memberExpr = memberExpression as MemberExpression;
if (memberExpr == null && memberExpression is LambdaExpression)
{
memberExpr = (memberExpression as LambdaExpression).Body as MemberExpression;
}
return memberExpr != null ? memberExpr.Member : null;
}
}