我可以将局部变量捕获到LINQ表达式中作为常量而不是闭包引用吗?

Seb*_*ood 18 c# linq lambda

我想说

int x = magic(), y = moremagic();
return i => i + (x/y);
Run Code Online (Sandbox Code Playgroud)

并将x捕获为常量而不是变量引用.这个想法是x永远不会改变,所以当表达式稍后编译时,编译器可以进行常量折叠并产生更有效的代码 - 即x/y通过指针解引用到闭包记录中来计算一次而不是每次调用.

没有办法在方法中将x标记为只读,并且编译器不够聪明,无法检测到在创建表达式后它不会更改.

我不想手工构建表达式.有什么好主意吗?

更新:我最终使用奇妙的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jar*_*Par 4

不,在 C# 中没有办法做到这一点。编译器不支持通过 value / const 捕获变量。您也不能以这种方式在运行时将非常量值转换为常量值。

此外,C# 编译器仅在初始编译期间对已知常量值进行常量折叠。如果可以在运行时将值冻结为常量,则它不会参与编译器常量折叠,因为它发生在运行时。