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

标签 c# linq lambda

我想说

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

并将 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);
        }
    }
}

最佳答案

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

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

关于c# - 我可以将局部变量作为常量而不是闭包引用捕获到 LINQ 表达式中吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3991621/

相关文章:

c# 获取具有特定外键的用户列表

c# - 使用 linq 更改数组中的一个字段

c# - 删除 XML 中的流氓符号的最佳方法是什么?

linq - 在C#中使用Linq进行字符串替换

c# - 如何确保字符串中的任何位置不超过一个空格?

c# - 根据拼写距离按属性区分

java - 将 lambda 分配给功能接口(interface)变量 : inherited abstract method must be implemented. 时出错,为什么?

c# - 使用 rhino mock 的 lambda 单元测试失败

c# - Java <-> Java/C#/Objective-C 之间的通信

c# - 使用 await 和 async 在 long 方法运行时释放 UI 线程