2010-02-25 97 views
10

我正在創建一個Validator<T>類。我試圖爲我的驗證器實現Linq SelectMany擴展方法,以便能夠使用Linq查詢組合表達式並驗證最終結果,即使基礎值發生更改。如何撰寫Linq表達式?即Func <Exp <Func<X, Y>>,Exp <Func<Y, Z>>,Exp <Func<X, Z> >>

以下測試代碼演示了我的意圖。

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<T, bool>的,但我需要能夠以更,好了,功能性的方式共同組成的功能。

我有,例如,以下兩個表達式:

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

一般情況下的功能可以修改爲接受不同數量的通用理論根據需要組成任何功能。

我已經搜索堆棧溢出(當然)和網絡,但沒有一個例子解決了這個問題。

我的Validator<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. 
     } 
    } 
} 

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); 
} 

在此先感謝!

+0

真的很難看到你的意思是「以更多功能的方式」。你需要做什麼,你不能通過刪除所有Expression <> .Compile()和.Invoke()來做到這一點? – pdr 2010-02-25 01:04:10

+1

我很好奇你爲什麼要驗證*表達式來產生值*,而不是簡單地驗證*實際值*。你能詳細說明這一點嗎? – Aaronaught 2010-02-25 01:16:48

+0

下面是一個例子 - 我試圖編寫函數,如f(x)= x + 1&g(x)= sqrt(x),然後h(x)= f(g(x))。現在,如果我對g有一個約束,使得x> = 0(-ve數等的sqrt),那麼我希望該約束傳播給函數h。當我的基礎值x變化時,我希望能夠問函數h是否應該考慮其結果仍然有效。 (這是一個有點人爲的例子,但它應該有助於澄清。)乾杯。 – Enigmativity 2010-02-25 09:21:41

回答

16

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" 
+0

這看起來非常像我之後的事情。乾杯。現在我可以繼續我的下一個頭痛。 Monads瘋了! – Enigmativity 2010-02-25 09:23:54

+3

我們在我們的內部庫中有一整套這樣的操作符。這是一個恥辱微軟不包括的東西,使表達>真棒(相對來說) – sinelaw 2012-08-09 13:22:52

12

雖然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, "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) { 
     if (node == From) { 
      return To; 
     } 
     return base.Visit(node); 
    } 
} 

這將替換第一個參數的每一個實例中,在第二個表達式表達的第一個表達式。所以像這樣的電話:

Compose((Class1 c) => c.StringProperty, (Class2 c2) => c2.Class1Property

將產生表達(Class2 c2) => c2.Class1Property.StringProperty

相關問題