«Введите» одно выражение в другое - PullRequest
2 голосов
/ 27 сентября 2019

У меня есть одно выражение, которое определено так:

Expression<Func<T1, T2, bool>> firstExpression;

и другое, подобное этому:

Expression<Func<T1, bool>> secondExpression;

T2 - это значение, которое я знаю, T1 isn«т.Я хотел бы сделать первое выражение вторым, учитывая значение параметра T2.Если бы это был обычный Linq, это было бы так:

var t2 = "Something I know";
secondExpression = t1 => fistExpression(t1, t2);

Как бы я это сделал, используя System.Linq.Expressions?

Ответы [ 3 ]

2 голосов
/ 27 сентября 2019

Вы можете сделать это с помощью выражения посетитель:

public static class EmitUtils
{
        private class ParameterReplacerVisitor : ExpressionVisitor
        {
            private ParameterExpression _source;
            private Expression _target;

            public ParameterReplacerVisitor(ParameterExpression source, Expression target)
            {
                _source = source;
                _target = target;
            }

            public override Expression Visit(Expression node) =>
                node == _source
                    ? _target
                    : base.Visit(node);
        }

        public static Expression ReplaceParameter(Expression body, ParameterExpression srcParameter, Expression dstParameter) =>
            new ParameterReplacerVisitor(srcParameter, dstParameter).Visit(body);

        public static Expression<Func<T1, T3>> BuildClosure<T1, T2, T3>(Expression<Func<T1, T2, T3>> src, T2 closureValue)
        {
            var constExpression = Expression.Constant(closureValue, typeof(T2));
            var body = ReplaceParameter(src.Body, src.Parameters[1], constExpression);
            return Expression.Lambda<Func<T1, T3>>(body, src.Parameters[0]);
        }

    }

Вот пример использования:

        [Test]
        public void ClosureTest()
        {
            Expression<Func<int, string, bool>> CheckStringLength = (len, str) => str.Length < len;
            var constString = "some string";
            var result = EmitUtils.BuildClosure(CheckStringLength, constString);
            Assert.That(result.Compile().Invoke(100), Is.True);
        }
1 голос
/ 27 сентября 2019

Вы можете сделать это, поменяв выражение параметра с константным выражением.Должно выглядеть так:

  1. реализовать пользовательский своп ExpressionVisitor

    public class SwapVisitor : ExpressionVisitor
    {
        public Expression From { get; set; }
        public Expression To { get; set; }
    
        public override Expression Visit(Expression node)
        {
            return node == From ? To : base.Visit(node);
        }
    }
    
  2. заменить параметр T2 на постоянную

    var swapper = new SwapVisitor
    {
      From = fistExpression.Parameters[1],
      To = Expression.Constant(val)
    };
    
    var result = Expression.Lambda<Func<T, bool>>(
       swapper.Visit(fistExpression.Body), 
       fistExpression.Parameters[0]);
    
0 голосов
/ 27 сентября 2019

Рассмотрим следующий пример (firstLambda и secondExpression нужны только для того, чтобы увидеть, как компилятор создает выражение)

using System;
using System.Linq.Expressions;

static void Main(string[] args)
{
    Expression<Func<string, int, bool>> firstExpression = (a, b) => a == b.ToString();

    Func<string, int, bool> firstLambda = (a, b) => a == b.ToString();
    Expression<Func<string, bool>> secondExpression = s => firstLambda(s, 45); // this expression I need just to see how it is compiled


    var inputParameter = Expression.Parameter(typeof(string), "s");

    var invocation = Expression.Invoke(firstExpression, inputParameter, Expression.Constant(47));

    var ourBuildExpression = Expression.Lambda<Func<string, bool> > (invocation, new ParameterExpression[] { inputParameter }).Compile();

    Console.WriteLine(ourBuildExpression("45"));
    Console.WriteLine(ourBuildExpression("47"));

    Console.ReadKey();
}

Я проверил в окне просмотра отладчика результат Expression<Func<string, bool>> secondExpression = s => firstLambda(s, 45); это было выражение Invoke, поэтому япостроил то же самое вручную.

И вы можете увидеть, как тестовые вызовы возвращаются и выдают False и True, как и ожидалось.

...