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

发布于 2024-09-28 05:18:32 字数 2233 浏览 1 评论 0原文

我想说的是

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

I'd like to say

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

and have the x be captured as a constant instead of a variable reference. The idea is that x will never change and so when the expression is later compiled, the compiler to can do constant folding and produce more efficient code -- i.e. calculating x/y once instead of on every call, via pointer dereferences into a closure record.

There is no way to mark x as readonly within a method, and the compiler is not clever enough to detect that it doesn't change after the creation of the expression.

I'd hate to have to build the expression by hand. Any brilliant ideas?

UPDATE: I ended up using the marvelous LinqKit to build a partial evaluator that will do the substitutions I want. The transform is only safe if you know that relevant references will not change, but it worked for my purposes. It is possible to restrict the partial evaluation only to direct members of your closure, which you control, by adding an extra check or two in there, which is fairly obvious on inspection of the sample code provided in the 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);
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

汐鸠 2024-10-05 05:18:32

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

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

No there is no way to do this in C#. The compiler does not support capturing variables by value / const. Nor can you convert a non-const value into a const value at runtime in this manner.

Additionally the C# compiler only does constant folding during the initial compilation for known constant values. If it was possible to freeze a value at runtime into a constant it wouldn't participate in compiler constant folding because it happens at runtime.

最美不过初阳 2024-10-05 05:18:32

编译器不执行这种类型的“值缓存”。常量折叠仅在编译时针对常量完成,不适用于只读字段,当然也不适用于在编译时没有已知值的局部变量。

您必须自己制作它,但它必须保留闭包引用(因为该值实际上在编译时无法确定,这就是为什么在构建表达式时它可能会被放入闭包中):

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

The compiler doesn't do this type of "value caching". Constant folding is done at compile time for constants only, not for readonly fields and certainly not for local variables which do not have a known value at compile time.

You have to make this yourself, but it has to stay a closure reference (since the value is in fact not determinable at compile time, which is why it is likely to be put in the closure when the expression is built):

int x = magic(), y = moremagic();
int xy = x/y;
return i => i + xy;
苏辞 2024-10-05 05:18:32

x 不能是常量,因为您正在执行运行时魔法来确定它是什么。但是,如果您知道 xy 不会改变,请尝试:

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

我还应该提到,即使 i => 的编译 IL 代码是这样的。 i + (x/y) 将显示除法,JIT 编译器几乎肯定会对其进行优化。

x can't be a constant, because you're doing runtime magic to determine what it is. However, if you know that x and y don't change, try:

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

I should also mention that even though the compiled IL code for i => i + (x/y) will show the division, the JIT compiler is almost certainly going to optimize this away.

歌枕肩 2024-10-05 05:18:32

我在 vb2005 中使用的一项技术是使用通用委托工厂来实现按值闭包。我只为子函数而不是函数实现了它,但也可以为函数实现它。如果以这种方式扩展:

FunctionOf.NewInv()

将是一个静态函数,它接受函数(稍后描述)、T3 和 T4 作为参数。传入的函数应接受类型 T2、T3 和 T4 的参数,并返回 T1。 NewInv 返回的函数将接受一个 T2 类型的参数,并使用该参数以及提供给 NewInv 的参数调用传入的函数。

调用看起来像:

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

One technique I used in vb2005 was to use a generic delegate factory to effect by-value closures. I only implemented it for subs rather than functions, but it could be done for functions as well. If extended in that way:

FunctionOf.NewInv()

would be a static function which would accept as parameters a function (described later), a T3, and a T4. The passed-in function should accept parameters of types T2, T3, and T4, and return a T1. The function returned by NewInv would accept one parameter of type T2 and call the passed-in function with that parameter and the ones given to NewInv.

The invocation would look something like:

return FunctionOf.NewInv((i,x,y) => i+x/y, x, y)
仅冇旳回忆 2024-10-05 05:18:32

如果您(像我一样)正在为 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);

If you(like me) are creating some expression builder for SQL queries you may consirer the following: first create a class variable, make it a constant and then access it like this:

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文