Как составить выражения Linq? т.е. Func‹Exp‹Func‹X, Y››, Exp‹Func‹Y, Z››, Exp‹Func‹X, Z›››

Я создаю класс Validator<T>. Я пытаюсь реализовать методы расширения Linq SelectMany для моего валидатора, чтобы иметь возможность составлять выражения с использованием запроса 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> вместе, используя выражение And, но мне нужно иметь возможность составлять функции вместе более, ну, функциональным способом.

У меня есть, например, следующие два выражения:

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 (конечно) и в Интернете, но не нашел примера, который решает эту проблему.

Мой код для класса 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);
}

Заранее спасибо!


person Enigmativity    schedule 25.02.2010    source источник
comment
Действительно изо всех сил пытаюсь понять, что вы имеете в виду, более функциональным способом. Что вам нужно сделать, чего вы не можете сделать, просто удалив все Expression‹› .Compile() и .Invoke()?   -  person pdr    schedule 25.02.2010
comment
Мне любопытно, почему вы хотите проверить выражение для получения значения, а не просто проверить фактическое значение. Можете ли вы уточнить этот момент?   -  person Aaronaught    schedule 25.02.2010
comment
Вот пример: я пытаюсь составить такие функции, как f(x) = x + 1 & g(x) = sqrt(x), тогда h(x) = f(g(x)). Теперь, если у меня есть ограничение на g такое, что x ›= 0 (sqrt из -ve чисел и т. д.), то я хочу, чтобы это ограничение распространялось на функцию h. Когда мое базовое значение x изменяется, я хочу иметь возможность спросить функцию h, должен ли я считать ее результат все еще действительным. (Это несколько надуманный пример, но он должен помочь прояснить ситуацию.) Ура.   -  person Enigmativity    schedule 25.02.2010
comment
Я думаю, что это предложение по обмену стеками может быть интересно ты. Если это так, покажите свою поддержку и помогите запустить бета-версию.   -  person greatwolf    schedule 17.01.2011


Ответы (2)


Эквивалент оператора композиции функций в Haskell.

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

в С#, вероятно, будет что-то вроде

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"
person dtb    schedule 25.02.2010
comment
Это очень похоже на то, что я искал. Ваше здоровье. Теперь я могу заняться следующей головной болью. Монады сошли с ума! - person Enigmativity; 25.02.2010
comment
У нас есть целый набор таких операторов в нашей внутренней библиотеке. Жаль, что Microsoft не включает вещи, которые делают Expression‹Func‹..›› действительно потрясающим (относительно говоря) - person sinelaw; 09.08.2012

Хотя ответ dtb работает для нескольких сценариев, он неоптимален, поскольку такое выражение нельзя использовать в Entity Framework, поскольку оно не может обрабатывать 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, 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);
    }
}

Это заменяет каждый экземпляр первого параметра в первом выражении выражением во втором выражении. Итак, такой вызов:

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

Получится выражение (Class2 c2) => c2.Class1Property.StringProperty.

person felipe    schedule 29.10.2014
comment
Кстати, это справедливо только в том случае, если f и g — чистые функции. Если вы используете это для Entity framework, скорее всего, они уже есть. В противном случае вы можете столкнуться с проблемами с побочными эффектами. - person ZeroUltimax; 20.12.2018