Могу ли я записать локальную переменную в выражение LINQ как константу, а не ссылку закрытия? - PullRequest
18 голосов
/ 22 октября 2010

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

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

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

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

Я быНенавижу строить выражения вручную.Какие-нибудь блестящие идеи?

ОБНОВЛЕНИЕ : я закончил тем, что использовал изумительный 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);
        }
    }
}

Ответы [ 5 ]

4 голосов
/ 22 октября 2010

Нет, в C # нет способа сделать это.Компилятор не поддерживает захват переменных по значению / const.Вы также не можете таким образом преобразовать неконстантное значение в константное значение во время выполнения.

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

2 голосов
/ 22 октября 2010

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

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

int x = magic(), y = moremagic();
int xy = x/y;
return i => i + xy;
0 голосов
/ 16 декабря 2016

Если вы (как и я) создаете конструктор выражений для запросов 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);
0 голосов
/ 22 октября 2010

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

FunctionOf.NewInv()

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

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

return FunctionOf.NewInv((i,x,y) => i+x/y, x, y)
0 голосов
/ 22 октября 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...