2010-02-25 4 views
10

Je crée une classe Validator<T>. J'essaie d'implémenter les méthodes d'extension Linq SelectMany pour que mon validateur puisse composer des expressions à l'aide d'une requête Linq et valider le résultat final même lorsque les valeurs sous-jacentes changent.Comment composer des expressions Linq? ie Func <Exp <Func<X, Y>>, Exp <Func<Y, Z>>, Exp <Func<X, Z> >>

Le code de test suivant illustre mon intention.

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

Je l'ai vu la question suivante How do I compose existing Linq Expressions qui me montre comment composer deux « s ensemble Func<T, bool> en utilisant une expression And, mais je dois être en mesure de composer des fonctions ensemble dans un plus, eh bien, de manière fonctionnelle.

J'ai, par exemple, les deux expressions suivantes:

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

Je souhaite créer une nouvelle expression comme ceci:

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

Plus brièvement, je suis en train de créer ces fonctions:

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

La fonction générale de cas peut être modifiée pour accepter différents nombres d'arguments génériques ts au besoin pour composer n'importe quelle fonction.

J'ai cherché Stack Overflow (bien sûr) et le web, mais je n'ai pas un exemple qui résout ce problème.

Mon code pour la classe Validator<T> est ci-dessous.

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

Mes SelectMany extensions contiennent des charges de dégueu .Compile().Invoke() que je veux me débarrasser de.

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

Merci d'avance!

+0

Vraiment du mal à voir ce que vous entendez par "d'une manière plus fonctionnelle". Que devez-vous faire que vous ne pouvez pas faire en supprimant simplement tous les Expression <> .Compile() et .Invoke() s? – pdr

+1

Je suis curieux de savoir pourquoi vous voudriez valider une expression * pour produire la valeur * par opposition à simplement valider une * valeur réelle *. Pouvez-vous élaborer sur ce point? – Aaronaught

+0

Voici un exemple - J'essaie de composer des fonctions telles que f (x) = x + 1 & g (x) = sqrt (x) puis h (x) = f (g (x)). Maintenant, si j'ai une contrainte sur g telle que x> = 0 (sqrt de -ve nombres etc.) alors je veux que cette contrainte se propage à la fonction h. Quand ma valeur sous-jacente de x change je veux pouvoir demander la fonction h si je devrais considérer son résultat comme étant toujours valide. (Ceci est un exemple un peu artificiel, mais cela devrait aider à clarifier.) Bravo. – Enigmativity

Répondre

16

L'équivalent de la composition de la fonction de Haskell opérateur

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

serait en C# probablement quelque chose comme

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

Est-ce que vous cherchez?

Exemple:

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

Cela ressemble beaucoup à ce que je recherchais. À votre santé. Maintenant, je peux continuer avec mon prochain mal de tête. Les monades sont fous! – Enigmativity

+3

Nous avons tout un ensemble de tels opérateurs dans notre lib interne. C'est dommage que Microsoft n'inclus pas de trucs pour faire l'Expression > vraiment génial (relativement parlant) – sinelaw

12

Alors que dtb's réponse fonctionne pour plusieurs scénarios, il est suboptimale que cette expression ne peut pas être utilisé dans Entity Framework, car il ne peut pas gérer Invoke appels. Malheureusement, pour éviter les appels dont on a besoin d'un code beaucoup plus, y compris une nouvelle classe dérivée 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); 
    } 
} 

Cette remplace toutes les occurrences du premier paramètre dans la première expression avec l'expression dans la deuxième expression.Ainsi, un appel comme celui-ci:

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

céderais l'expression (Class2 c2) => c2.Class1Property.StringProperty.

Questions connexes