Question

Je crée une classe Validator<T>. Je tente de mettre en œuvre les méthodes d'extension SelectMany Linq pour mon validateur pour être en mesure de composer des expressions à l'aide d'une requête Linq et de valider le résultat final, même si les valeurs sous-jacentes changent.

Le code de test suivant montre 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 Comment Linq-je rédiger des expressions existantes qui me montre comment composer deux de Func<T, bool> ensemble en utilisant une expression And, mais je dois être en mesure de composer ensemble des fonctions dans un plus, 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 succinctement, 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 de cas général peut être modifié pour accepter un nombre différent d'arguments génériques nécessaires pour composer une fonction.

J'ai recherché Stack Overflow (bien sûr) et le web, mais pas un exemple qui permet de résoudre 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 extensions SelectMany 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!

Était-ce utile?

La solution

L'équivalent de la composition de Haskell fonction 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"

Autres conseils

Alors que DTB fonctionne la réponse pour plusieurs scénarios, il est suboptimale que cette expression ne peut pas être utilisé dans Entity Framework, comme il ne peut pas traiter les appels Invoke. Malheureusement, pour éviter ces appels il faut beaucoup plus de code, y compris une nouvelle classe dérivée de 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);
    }
}

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top