代码之家  ›  专栏  ›  技术社区  ›  Enigmativity

如何编写Linq表达式?ie Func<Exp<Func<X,Y>>,Exp<Func<Y,Z>>,Exp<Func<X,Z>>

  •  14
  • Enigmativity  · 技术社区  · 16 年前

    我在创造一个 Validator<T> 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 它教我如何作曲 Func<T, bool> 我们一起使用 And 表达式,但我需要能够以一种更实用的方式将函数组合在一起。

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

    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>>>
    

    可以根据需要修改general case函数,以接受不同数量的泛型参数来组成任何函数。

    我搜索过堆栈溢出(当然)和web,但是没有一个例子可以解决这个问题。

    验证程序<T> 班级在下面。

    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.
            }
        }
    }
    

    我的 选择多个 .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);
    }
    

    2 回复  |  直到 8 年前
        1
  •  20
  •   dtb    16 年前

    相当于哈斯克尔的 操作人员

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

    可能是

    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"
    
        2
  •  20
  •   Ian Kemp    7 年前

    虽然 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) => c.StringProperty, (Class2 c2) => c2.Class1Property

    将产生表达式 (Class2 c2) => c2.Class1Property.StringProperty .