AutoMapper for Func在选择器类型之间

Byr*_*ahl 7 c# linq

我有两种类型:CatDog.我想用一个选择猫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.
}
Run Code Online (Sandbox Code Playgroud)

要么已经有了这样或应该有的东西.

luk*_*san 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;
}
Run Code Online (Sandbox Code Playgroud)

更新:自从我第一次回答这个问题已经有1.5年了,我想我现在会扩展我的答案,因为当你有一个表达而不是委托时,人们会问如何做到这一点.

该解决方案在原理上是一样的-我们需要能够组成两个函数(selectormapper)到一个单一的功能.不幸的是,由于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;
}
Run Code Online (Sandbox Code Playgroud)

因此,创建我们的组合函数的唯一方法是使用类自己构建表达式树System.Linq.Expressions.

我们真正需要做的是修改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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我创建了一个扩展方法,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]);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,有了所有基础设施,我们可以修改我们的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;
}
Run Code Online (Sandbox Code Playgroud)

我创建了一个简单的控制台应用程序来测试它.希望我的解释不会太混淆; 但不幸的是,没有一种更简单的方法可以做你想做的事情.如果你仍然完全困惑,至少你可以重用我的代码,并对表达树处理的细微差别和复杂性有所了解!