Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/304.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 我可以将局部变量作为常量而不是闭包引用捕获到LINQ表达式中吗?_C#_Linq_Lambda - Fatal编程技术网

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

C# 我可以将局部变量作为常量而不是闭包引用捕获到LINQ表达式中吗?,c#,linq,lambda,C#,Linq,Lambda,我想说 int x = magic(), y = moremagic(); return i => i + (x/y); 并将x捕获为常量,而不是变量引用。其思想是x永远不会改变,因此,当以后编译表达式时,编译器可以进行常量折叠并生成更高效的代码——即,计算x/y一次,而不是每次调用时,通过将指针解引用到闭包记录中 在一个方法中,没有办法将x标记为只读,而且编译器也不够聪明,无法检测到它在创建表达式后没有改变 我不喜欢用手来构建表达式。有什么好主意吗 更新:我最终使用marvelous构

我想说

int x = magic(), y = moremagic();
return i => i + (x/y);
并将x捕获为常量,而不是变量引用。其思想是x永远不会改变,因此,当以后编译表达式时,编译器可以进行常量折叠并生成更高效的代码——即,计算
x/y
一次,而不是每次调用时,通过将指针解引用到闭包记录中

在一个方法中,没有办法将x标记为只读,而且编译器也不够聪明,无法检测到它在创建表达式后没有改变

我不喜欢用手来构建表达式。有什么好主意吗

更新:我最终使用marvelous构建了一个部分求值器,该求值器将执行我想要的替换。只有当您知道相关引用不会更改时,转换才是安全的,但它符合我的目的。通过在其中添加一两个额外的检查,可以将部分求值限制为仅对闭包的直接成员(由您控制),这在检查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);
        }
    }
}
///遍历表达式,急切地计算属性/字段成员,并用常量替换它们。
///您必须确保这些字段(例如,对闭包中捕获的变量的引用)在语义上是正确的
///将永远不会更改,但它允许通过将常量数转换为真常量来更高效地编译表达式,
///编译器可以折叠它。
公共类PartiallyEvaluageMemberExpressionVisitor:ExpressionVisitor
{
受保护的重写表达式VisitMemberAccess(MemberExpression m)
{
表达式exp=本次访问(m.Expression);
如果(exp==null | | exp是ConstantExpression)//null=静态成员
{
object@object=exp==null?null:((ConstantExpression)exp).Value;
对象值=null;类型类型=null;
如果(m.成员为FieldInfo)
{
FieldInfo fi=(FieldInfo)m.会员;
value=fi.GetValue(@object);
type=fi.FieldType;
}
else if(m.成员为PropertyInfo)
{
PropertyInfo pi=(PropertyInfo)m.Member;
if(pi.GetIndexParameters().Length!=0)
抛出新ArgumentException(“无法消除对索引属性的闭包引用”);
value=pi.GetValue(@object,null);
type=pi.PropertyType;
}
返回表达式.常量(值,类型);
}
否则,就把它传过去
{
返回表达式.MakeMemberAccess(exp,m.Member);
}
}
}

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

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


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


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

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

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

我还应该提到,尽管编译后的I=>I+(x/y)的IL代码将显示除法,但JIT编译器几乎肯定会对此进行优化。

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

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

调用将类似于:

return FunctionOf.NewInv((i,x,y) => i+x/y, x, y) .NewInv的返回函数((i,x,y)=>i+x/y,x,y) 如果您(像我一样)正在为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);

事实上,尽管这仍然需要在每次使用中查找xy。我的例子是任意复杂的——最好让C#编译器简化得到的大型方程,而不是自己去做。我不认为它可以优化除法,因为它必须每次从闭包中提取x和y,也许你是对的。我对.NET的JIT编译器了解不多。在我看来,编译器很容易认识到,对于闭包的特定实例,x和y的值永远不会改变(它们基本上保存为基于IL的新类上的只读字段),并在运行时修改该类以消除对它们的需要。这样做与使用闭包相比没有任何好处。@Servy如何使用表达式创建闭包?使用Expression.Constant的查询中有值。现在有>@Parameters您使用lambda并关闭变量,如like all所示
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);