Как построить LambdaExpression из существующего LambdaExpression без компиляции - PullRequest
5 голосов
/ 19 марта 2011

Я хочу объединить два лямбда-выражения без их компиляции.

Вот как это выглядит, если я их скомпилирую:

    public Expression<Func<TContainer,bool>> CreatePredicate<TContainer,TMember>(
        Expression<Func<TContainer,TMember>> getMemberExpression, 
        Expression<Func<TMember,bool>> memberPredicateExpression)
    {
        return x => memberPredicateExpression.Compile()(getMemberExpression.Compile()(x));
    }

Это явно не самый быстрый способ получить целевое выражение из предоставленных аргументов. Кроме того, это делает его несовместимым с поставщиками запросов, такими как LINQ to SQL, которые не поддерживают вызовы методов C #.

Из того, что я прочитал, кажется, что лучший подход - создать класс ExpressionVisitor. Тем не менее, похоже, что это может быть довольно распространенной задачей. Кто-нибудь знает о существующей базе с открытым исходным кодом, которая обеспечивает такую ​​функциональность? Если нет, то как лучше всего подойти к ExpressionVisitor, чтобы сделать его как можно более универсальным?

1 Ответ

4 голосов
/ 19 марта 2011

Я не знаю, лучший ли это способ, но вы могли бы сделать что-то подобное:

public Expression<Func<TContainer,bool>> CreatePredicate<TContainer,TMember>(
    Expression<Func<TContainer,TMember>> getMemberExpression, 
    Expression<Func<TMember,bool>> memberPredicateExpression)
{
    ParameterExpression x = Expression.Parameter(typeof(TContainer), "x");
    return Expression.Lambda<Func<TContainer, bool>>(
        Expression.Invoke(
            memberPredicateExpression,
            Expression.Invoke(
                getMemberExpression,
                x)),
        x);
}

Использование:

var expr = CreatePredicate(
    (Foo f) => f.Bar,
    bar => bar % 2 == 0);

Результат:

x => Invoke(bar => ((bar % 2) == 0), Invoke(f => f.Bar, x))

Полагаю, было бы лучше получить что-то вроде x => x.Bar % 2 == 0, но, вероятно, будет значительно сложнее ...


РЕДАКТИРОВАТЬ: на самом деле это было не так сложно с посетителем выражения:

public Expression<Func<TContainer,bool>> CreatePredicate<TContainer,TMember>(
    Expression<Func<TContainer,TMember>> getMemberExpression, 
    Expression<Func<TMember,bool>> memberPredicateExpression)
{
    return CombineExpressionVisitor.Combine(
        getMemberExpression,
        memberPredicateExpression);
}

class CombineExpressionVisitor : ExpressionVisitor
{
    private readonly ParameterExpression _parameterToReplace;
    private readonly Expression _replacementExpression;
    private CombineExpressionVisitor(ParameterExpression parameterToReplace, Expression replacementExpression)
    {
        _parameterToReplace = parameterToReplace;
        _replacementExpression = replacementExpression;
    }

    public static Expression<Func<TSource, TResult>> Combine<TSource, TMember, TResult>(
        Expression<Func<TSource, TMember>> memberSelector,
        Expression<Func<TMember, TResult>> resultSelector)
    {
         var visitor = new CombineExpressionVisitor(
            resultSelector.Parameters[0],
            memberSelector.Body);
        return Expression.Lambda<Func<TSource, TResult>>(
            visitor.Visit(resultSelector.Body),
            memberSelector.Parameters);
    }

    protected override Expression VisitParameter(ParameterExpression parameter)
    {
        if (parameter == _parameterToReplace)
            return _replacementExpression;
        return base.VisitParameter(parameter);
    }
}

Это дает следующее выражение:

f => ((f.Bar % 2) == 0)
...