Могу ли я захватить локальную переменную в выражение LINQ как константу, а не ссылку на замыкание?

Я бы хотел сказать

int x = magic(), y = moremagic();
return i => i + (x/y);

и x будет фиксироваться как константа вместо ссылки на переменные. Идея состоит в том, что x никогда не будет изменяться, и поэтому, когда выражение будет скомпилировано позже, компилятор может выполнять постоянную фальцовку и создавать более эффективный код, т.е. Вычислять x/y один раз вместо каждого вызова, посредством разметки указателя в запись закрытия,

Невозможно пометить x как readonly внутри метода, а компилятор не достаточно умен, чтобы обнаружить, что он не изменяется после создания выражения.

Мне не хотелось бы создавать выражение вручную. Любые блестящие идеи?

UPDATE. Я закончил использование чудесного LinqKit для создания частичного оценщика, который будет выполнять замены Я хочу. Преобразование является безопасным только в том случае, если вы знаете, что соответствующие ссылки не изменятся, но это сработало для моих целей. Можно ограничить частичную оценку только для того, чтобы направить членов вашего закрытия, которыми вы управляете, добавив дополнительную проверку или два там, что довольно очевидно при проверке кода примера, представленного в LinqKit.

/// <summary>Walks your expression and eagerly evaluates property/field members and substitutes them with constants.
/// You must be sure this is semantically correct, by ensuring those fields (e.g. references to captured variables in your closure)
/// will never change, but it allows the expression to be compiled more efficiently by turning constant numbers into true constants, 
/// which the compiler can fold.</summary>
public class PartiallyEvaluateMemberExpressionsVisitor : ExpressionVisitor
{
    protected override Expression VisitMemberAccess(MemberExpression m)
    {
        Expression exp = this.Visit(m.Expression);

        if (exp == null || exp is ConstantExpression) // null=static member
        {
            object @object = exp == null ? null : ((ConstantExpression)exp).Value;
            object value = null; Type type = null;
            if (m.Member is FieldInfo)
            {
                FieldInfo fi = (FieldInfo)m.Member;
                value = fi.GetValue(@object);
                type = fi.FieldType;
            }
            else if (m.Member is PropertyInfo)
            {
                PropertyInfo pi = (PropertyInfo)m.Member;
                if (pi.GetIndexParameters().Length != 0)
                    throw new ArgumentException("cannot eliminate closure references to indexed properties");
                value = pi.GetValue(@object, null);
                type = pi.PropertyType;
            }
            return Expression.Constant(value, type);
        }
        else // otherwise just pass it through
        {
            return Expression.MakeMemberAccess(exp, m.Member);
        }
    }
}

Ответ 1

Нет никакого способа сделать это на С#. Компилятор не поддерживает захват переменных по значению /const. Также вы не можете преобразовать значение non-const в значение const во время выполнения таким образом.

Кроме того, компилятор С# выполняет постоянную фальцовку во время начальной компиляции для известных постоянных значений. Если бы можно было заморозить значение во время выполнения в константу, он не будет участвовать в сгибании константы компилятора, потому что это происходит во время выполнения.

Ответ 2

Компилятор не выполняет этот тип кэширования значений. Постоянное свертывание выполняется во время компиляции только для констант, а не для полей readonly и, конечно, не для локальных переменных, которые не имеют известного значения во время компиляции.

Вы должны сделать это сами, но он должен оставаться ссылкой на замыкание (поскольку значение на самом деле не определено во время компиляции, поэтому его можно поместить в закрытие при построении выражения):

int x = magic(), y = moremagic();
int xy = x/y;
return i => i + xy;

Ответ 3

x не может быть константой, потому что вы используете магию времени выполнения, чтобы определить, что это такое. Однако, если вы знаете, что x и y не изменяются, попробуйте:

int x = magic(), y = moremagic();
int xOverY = x/y;
return i => i + xOverY;

Следует также упомянуть, что даже если скомпилированный код IL для i => i + (x/y) покажет деление, компилятор JIT почти наверняка собирается оптимизировать это.

Ответ 4

Один метод, который я использовал в vb2005, заключался в том, чтобы использовать общий делегат factory для создания закрытий на основе значений. Я реализовал его только для субтитров, а не для функций, но это можно было бы сделать и для функций. Если это расширено:

FunctionOf.NewInv()

будет статической функцией, которая будет принимать в качестве параметров функцию (описанную ниже), T3 и T4. Передаваемая функция должна принимать параметры типов T2, T3 и T4 и возвращать T1. Функция, возвращаемая NewInv, будет принимать один параметр типа T2 и вызывать переданную функцию с этим параметром и теми, которые заданы для NewInv.

Вызов будет выглядеть примерно так:

return FunctionOf.NewInv((i,x,y) => i+x/y, x, y)

Ответ 5

Если вы (как и я) создаете некоторый построитель выражений для SQL-запросов, вы можете потребовать следующее: сначала создайте переменную класса, сделайте ее константой, а затем получите доступ к ней следующим образом:

var constant= Expression.Constant(values);
var start = Expression.MakeMemberAccess(constant, values.GetMemberInfo(f => f.Start));
var end = Expression.MakeMemberAccess(constant, values.GetMemberInfo(f => f.End));

var more = Expression.GreaterThanOrEqual(memberBody, start);
var less = Expression.LessThanOrEqual(memberBody, end);