17

Validator<T>クラスを作成しています。SelectManyバリデーターが Linq クエリを使用して式を作成し、基になる値が変更された場合でも最終結果を検証できるように、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);

私は次の質問を見てきました既存のLinq式を作成するにはどうすればよいFunc<T, bool>ですか 式を使用して2つのを一緒に作成する方法を示していますAndが、より機能的な方法で関数を一緒に作成できるようにする必要があります。

たとえば、次の 2 つの式があります。

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

ジェネラル ケース関数は、任意の関数を作成するために必要に応じて、さまざまな数のジェネリック引数を受け入れるように変更できます。

Stack Overflow (もちろん) と Web を検索しましたが、この問題を解決する例はありません。

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

前もって感謝します!

4

2 に答える 2

21

dtbInvoke回答はいくつかのシナリオで機能しますが、呼び出しを処理できないため、このような式は Entity Framework で使用できないため、最適ではありません。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);
    }
}

これにより、最初の式の最初のパラメーターのすべてのインスタンスが 2 番目の式の式に置き換えられます。したがって、次のような呼び出し:

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

式が得られます(Class2 c2) => c2.Class1Property.StringProperty

于 2014-10-29T20:55:36.150 に答える
20

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"
于 2010-02-25T02:30:48.397 に答える