我想说
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/