2010-02-25 16 views
10

Ich erstelle eine Validator<T> Klasse. Ich versuche, die Linq Erweiterungs-Methoden für meine Validator zu implementieren, in der Lage, Ausdrücke mit einer Linq-Abfrage zu erstellen und das Endergebnis zu überprüfen, selbst wenn die zugrunde liegenden Werte ändern.Wie komponiere ich Linq Expressions? dh Func <Exp <Func<X, Y>>, Exp <Func<Y, Z>>, Exp <Func<X, Z> >>

Der folgende Testcode zeigt meine Absicht.

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

Ich habe die folgende Frage How do I compose existing Linq Expressions gesehen, die zeigt mir, wie zwei Func<T, bool> ‚s komponieren zusammen einen And Ausdruck, aber ich muß in der Lage sein, Funktionen zu komponieren zusammen in einem, nun ja, funktional.

Ich habe zum Beispiel die beiden folgenden Ausdrücke:

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

Ich möchte einen neuen Ausdruck wie folgt erstellen:

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

kurz und bündig diese Funktionen erstellen Ich versuche:

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

Die allgemeine Fallfunktion kann geändert werden, um verschiedene Nummern von generischem Argumen zu akzeptieren ts wie benötigt, um eine Funktion zu komponieren.

Ich habe Stack Overflow (natürlich) und das Web gesucht, aber kein Beispiel, das dieses Problem löst.

Mein Code für die Klasse Validator<T> ist unten.

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

Meine SelectMany Erweiterungen enthalten Lasten von ekligen .Compile().Invoke(), die ich loswerden will.

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

Vielen Dank im Voraus!

+0

Wirklich kämpfen, um zu sehen, was Sie "auf eine funktionellere Weise" bedeuten. Was würden Sie tun müssen, dass Sie nicht alle Expression <>. – pdr

+1

Ich bin neugierig, warum Sie einen * Ausdruck validieren sollten, um den Wert * zu erzeugen, anstatt einfach einen * tatsächlichen Wert * zu validieren. Können Sie diesen Punkt näher ausführen? – Aaronaught

+0

Hier ist ein Beispiel - ich versuche Funktionen wie f (x) = x + 1 & g (x) = sqrt (x) dann h (x) = f (g (x)) zu komponieren. Nun, wenn ich eine Einschränkung für g habe, so dass x> = 0 (sqrt von -ve-Zahlen usw.), dann möchte ich, dass diese Beschränkung sich auf die Funktion h ausbreitet. Wenn sich mein zugrunde liegender Wert von x ändert, möchte ich die Funktion h fragen können, wenn ich das Ergebnis als noch gültig betrachten sollte. (Dies ist ein etwas künstliches Beispiel, aber es sollte helfen, zu klären.) Prost. – Enigmativity

Antwort

16

Das Äquivalent der Haskell-Funktion Zusammensetzung Operator

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

in C# würde wahrscheinlich so etwas wie

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

Ist das, was Sie suchen?

Beispiel:

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

Das sieht ziemlich ähnlich aus wie ich es wollte. Prost. Jetzt kann ich mit meinem nächsten Kopfschmerz weitermachen. Monaden sind verrückt! – Enigmativity

+3

Wir haben eine ganze Reihe solcher Operatoren in unserer internen Bibliothek. Es ist eine Schande, dass Microsoft keine Sachen enthält, um den Ausdruck > wirklich genial zu machen (relativ gesehen) – sinelaw

12

Während dtb's Antwort für mehrere Szenarien funktioniert, ist es nicht optimal sein als ein solcher Ausdruck kann nicht in Entity Framework verwendet werden, da es nicht Invoke Anrufe verarbeiten kann. Leider diese Anrufe zu vermeiden man viel mehr Code benötigt, eine neue ExpressionVisitor abgeleiteten Klasse einschließlich:

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

Dies ersetzt jede Instanz des ersten Parameters im ersten Ausdruck mit dem Ausdruck in dem zweiten Ausdruck.So ein Aufruf wie folgt:

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

Würde der Ausdruck (Class2 c2) => c2.Class1Property.StringProperty ergeben.

Verwandte Themen