【问题标题】:How do I compose Linq Expressions? ie Func<Exp<Func<X, Y>>, Exp<Func<Y, Z>>, Exp<Func<X, Z>>>如何编写 Linq 表达式?即 Func<Exp<Func<X, Y>>, Exp<Func<Y, Z>>, Exp<Func<X, Z>>>
【发布时间】:2010-02-25 00:55:09
【问题描述】:

我正在创建一个Validator&lt;T&gt; 类。我正在尝试为我的验证器实现 Linq SelectMany 扩展方法,以便能够使用 Linq 查询编写表达式并验证最终结果,即使基础值发生变化。

以下测试代码展示了我的意图。

var a = 2;
var b = 3;

var va = Validator.Create(() => a, n => n >= 0 && n < 5);
var vb = Validator.Create(() => b, n => n >= 0 && n < 5);

var vc = from ia in va
         from ib in vb
         select ia + ib;

Debug.Assert(vc.Value == a + b); //2 + 3
Debug.Assert(vc.Value == 5);

Debug.Assert(vc.IsValid == true);

a = 7;

Debug.Assert(vc.Value == a + b); //7 + 3
Debug.Assert(vc.Value == 10);

Debug.Assert(va.IsValid == false);
Debug.Assert(vb.IsValid == true);
Debug.Assert(vc.IsValid == false);

我看到了以下问题How do I compose existing Linq Expressions,它向我展示了如何使用And 表达式将两个Func&lt;T, bool&gt; 组合在一起,但我需要能够以更有效的方式组合函数方式。

例如,我有以下两个表达式:

public Expression<Func<T>> ValueExpression { get; private set; }
public Expression<Func<T, bool>> ValidationExpression { get; private set; }

我希望创建一个这样的新表达式:

    public Expression<Func<bool>> IsValidExpression
    {
        get
        {
            // TODO: Compose expressions rather than compile & invoke.
        }
    }

更简洁地说,我正在尝试创建这些函数:

// Specific case
Func<Expression<Func<T>>, Expression<Func<T, bool>>, Expression<Func<bool>>>
// General case
Func<Expression<Func<X, Y>>, Expression<Func<Y, Z>>, Expression<Func<X, Z>>>

通用 case 函数可以根据需要修改为接受不同数量的通用参数来组成任何函数。

我已经搜索过 Stack Overflow(当然)和网络,但没有解决此问题的示例。

Validator&lt;T&gt; 类的代码如下。

public class Validator<T>
{
    public Validator(Expression<Func<T>> valueFunc,
        Expression<Func<T, bool>> validationFunc)
    {
        this.ValueExpression = valueFunc;
        this.ValidationExpression = validationFunc;
    }

    public Expression<Func<T>> ValueExpression { get; private set; }
    public Expression<Func<T, bool>> ValidationExpression { get; private set; }

    public T Value { get { return this.ValueExpression.Compile().Invoke(); } }

    public bool IsValid { get { return this.IsValidExpression.Compile().Invoke(); } }

    public Expression<Func<bool>> IsValidExpression
    {
        get
        {
            // TODO: Compose expressions.
        }
    }
}

我的SelectMany 扩展包含大量令人讨厌的.Compile().Invoke(),我想摆脱它们。

public static Validator<U> SelectMany<T, U>(this Validator<T> @this, Expression<Func<T, Validator<U>>> k)
{
    Expression<Func<T>> fvtv = @this.ValueExpression;
    Expression<Func<Validator<U>>> fvu = () => k.Compile().Invoke(fvtv.Compile().Invoke());
    Expression<Func<U>> fvuv = fvu.Compile().Invoke().ValueExpression;
    Expression<Func<U, bool>> fvtiv = u => @this.ValidationExpression.Compile().Invoke(fvtv.Compile().Invoke());
    return fvuv.ToValidator(fvtiv);
}

public static Validator<V> SelectMany<T, U, V>(this Validator<T> @this, Expression<Func<T, Validator<U>>> k, Expression<Func<T, U, V>> s)
{
    Expression<Func<Validator<U>>> fvu = () => @this.SelectMany(k);
    Expression<Func<T>> fvtv = @this.ValueExpression;
    Expression<Func<U>> fvuv = fvu.Compile().Invoke().ValueExpression;
    Expression<Func<T, bool>> fvtiv = @this.ValidationExpression;
    Expression<Func<U, bool>> fvuiv = u => fvu.Compile().Invoke().ValidationExpression.Compile().Invoke(u);
    Expression<Func<V>> fvv = () => s.Compile().Invoke(fvtv.Compile().Invoke(), fvuv.Compile().Invoke());
    Expression<Func<V, bool>> fvviv = v => fvtiv.Compile().Invoke(fvtv.Compile().Invoke()) && fvuiv.Compile().Invoke(fvuv.Compile().Invoke());
    return fvv.ToValidator(fvviv);
}

提前致谢!

【问题讨论】:

  • 真的很难理解“以更实用的方式”是什么意思。仅删除所有 Expression .Compile() 和 .Invoke() 无法完成的操作需要做什么?
  • 我很好奇你为什么要验证一个表达式来产生值,而不是简单地验证一个实际值。你能详细说明这一点吗?
  • 这是一个例子 - 我正在尝试组合函数,例如 f(x) = x + 1 & g(x) = sqrt(x) 然后 h(x) = f(g(x ))。现在,如果我对 g 有一个约束,使得 x >= 0(-ve 数的平方等),那么我希望该约束传播到函数 h。当我的 x 基础值发生变化时,我希望能够询问函数 h 我是否应该认为它的结果仍然有效。 (这是一个有些人为的例子,但它应该有助于澄清。)干杯。
  • 我认为您可能会对这个堆栈交换 proposal 感兴趣。如果它是显示您的支持并帮助它进入测试版。

标签: c# .net linq linq-expressions


【解决方案1】:

虽然dtb's 答案适用于多种情况,但它不是最理想的,因为这样的表达式不能在实体框架中使用,因为它无法处理Invoke 调用。不幸的是,为了避免这些调用,我们需要更多的代码,包括一个新的ExpressionVisitor 派生类:

static Expression<Func<A, C>> Compose<A, B, C>(Expression<Func<B, C>> f,
                                               Expression<Func<A, B>> g)
{
    var ex = ReplaceExpressions(f.Body, f.Parameters[0], g.Body);

    return Expression.Lambda<Func<A, C>>(ex, g.Parameters[0]);
}

static TExpr ReplaceExpressions<TExpr>(TExpr expression,
                                       Expression orig,
                                       Expression replacement)
    where TExpr : Expression 
{
    var replacer = new ExpressionReplacer(orig, replacement);

    return replacer.VisitAndConvert(expression, nameof(ReplaceExpressions));
}

private class ExpressionReplacer : ExpressionVisitor
{
    private readonly Expression From;
    private readonly Expression To;

    public ExpressionReplacer(Expression from, Expression to)
    {
        From = from;
        To = to;
    }

    public override Expression Visit(Expression node)
    {
        return node == From ? To : base.Visit(node);
    }
}

这会将第一个表达式中第一个参数的每个实例替换为第二个表达式中的表达式。所以这样的调用:

Compose((Class1 c) =&gt; c.StringProperty, (Class2 c2) =&gt; c2.Class1Property

将产生表达式(Class2 c2) =&gt; c2.Class1Property.StringProperty

【讨论】:

  • 附带说明,仅当“f”和“g”是纯函数时才有效。如果您将其用于实体框架,那么它们很可能已经存在。否则,您可能会遇到副作用问题。
【解决方案2】:

相当于Haskell的函数组合操作符

(.) :: (b->c) -> (a->b) -> (a->c)
f . g = \ x -> f (g x)

在 C# 中可能类似于

static Expression<Func<A, C>> Compose<A, B, C>(
    Expression<Func<B, C>> f,
    Expression<Func<A, B>> g)
{
    var x = Expression.Parameter(typeof(A));
    return Expression.Lambda<Func<A, C>>(
        Expression.Invoke(f, Expression.Invoke(g, x)), x);
}

这是你要找的吗?

例子:

Compose<int, int, string>(y => y.ToString(), x => x + 1).Compile()(10); // "11"

【讨论】:

  • 这看起来很像我所追求的。干杯。现在我可以继续我的下一个头痛了。单子很疯狂!
  • 我们的内部库中有一整套这样的运算符。很遗憾,Microsoft 没有包含使 Expression> 非常棒的东西(相对而言)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-13
  • 1970-01-01
相关资源
最近更新 更多