Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/279.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#闭包堆分配_C#_.net_Closures_Roslyn - Fatal编程技术网

方法开始时发生的C#闭包堆分配

方法开始时发生的C#闭包堆分配,c#,.net,closures,roslyn,C#,.net,Closures,Roslyn,我似乎遇到了C#编译器的一些奇怪行为 考虑以下代码示例: static void Main(string[] args) { Foo(false, 8); } public static void Foo(bool execute, int x) { if (execute) { Task.Run(() => Console.WriteLine(x)); } } 运行此(在发行版中)显示发生了一些意外的分配。检查IL表明,闭包触发的堆分配

我似乎遇到了C#编译器的一些奇怪行为

考虑以下代码示例:

static void Main(string[] args)
{
    Foo(false, 8);
}

public static void Foo(bool execute, int x)
{
    if (execute)
    {
        Task.Run(() => Console.WriteLine(x));
    }
}

运行此(在发行版中)显示发生了一些意外的分配。检查IL表明,闭包触发的堆分配出现在函数的最开始处,而不是在条件内:

  .method public hidebysig static void 
    Foo(
      bool execute, 
      int32 x
    ) cil managed 
  {
    .maxstack 2
    .locals init (
      [0] class Test.Program/'<>c__DisplayClass1_0' 'CS$<>8__locals0'
    )

    IL_0000: newobj       instance void Test.Program/'<>c__DisplayClass1_0'::.ctor()
    IL_0005: stloc.0      // 'CS$<>8__locals0'
    IL_0006: ldloc.0      // 'CS$<>8__locals0'
    IL_0007: ldarg.1      // x
    IL_0008: stfld        int32 Test.Program/'<>c__DisplayClass1_0'::x

    // [18 13 - 18 25]
    IL_000d: ldarg.0      // execute
    IL_000e: brfalse.s    IL_0022

    // [20 17 - 20 54]
    IL_0010: ldloc.0      // 'CS$<>8__locals0'
    IL_0011: ldftn        instance void Test.Program/'<>c__DisplayClass1_0'::'<Foo>b__0'()
    IL_0017: newobj       instance void [mscorlib]System.Action::.ctor(object, native int)
    IL_001c: call         class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Action)
    IL_0021: pop          

    // [22 9 - 22 10]
    IL_0022: ret          

  } // end of method Program::Foo
.method公共隐藏静态void
福(
布尔执行,
int32 x
)cil管理
{
.maxstack 2
.init(
[0]类测试。程序/'c_uudisplayClass1_0''CS$8_ulocals0'
)
IL_0000:newobj实例void Test.Program/'c_udisplayClass1_0':.ctor()
IL_0005:stloc.0/'CS$8__locals0'
IL_0006:ldloc.0/'CS$8__locals0'
IL_0007:ldarg.1//x
IL_0008:stfld int32 Test.Program/'c__DisplayClass1_0'::x
// [18 13 - 18 25]
IL_000d:ldarg.0//执行
ILU 000e:brfalse.s ILU 0022
// [20 17 - 20 54]
IL_0010:ldloc.0/'CS$8_ulocals0'
IL_0011:ldftn实例无效测试。程序/'c_显示类1_0'::'b_0'()
IL_0017:newobj实例无效[mscorlib]系统。操作::.ctor(对象,本机int)
IL_001c:调用类[mscorlib]System.Threading.Tasks.Task[mscorlib]System.Threading.Tasks.Task::Run(类[mscorlib]System.Action)
IL_0021:流行音乐
// [22 9 - 22 10]
IL_0022:ret
}//方法结束程序::Foo

我是不是遗漏了什么,有人能解释这种奇怪的行为吗?Roslyn是否可能生成为闭包分配的代码,而不管我们是否实际执行闭包?

这种行为是出于设计

当方法具有闭包时,闭包内使用的所有变量都必须是闭包类的一部分(以便lambda可以访问其当前值)

如果编译器没有立即分配闭包,那么在创建闭包实例时,它必须将本地变量中的值复制到闭包类上的字段,这将浪费时间和内存


如果多个具有不同可达性(或者更糟糕的是,嵌套范围)的lambda靠近同一个变量,这也会使codegen的风险更大、更复杂。

正如SLacks所述,这种行为是设计的,因为x是函数的一个参数

但是,分配可以“移动到”以下条件:

public static void Foo(bool execute, int x)
{
    if (execute)
    {
        int localx = x;
        Task.Run(() => Console.WriteLine(localx));
    }
}

在这个特定场景中,转换是安全的,因为x在Foo的主体内和lambda中都没有修改。此外,if语句不是在循环中执行的(在这种情况下,转换实际上可能会增加分配的数量)。编译器不会为您进行分析,但您可以。

闭包显然需要关闭它们使用的变量—我理解这一部分。我不明白的是,在方法开始时会发生什么操作,不能推迟到以后,当实际使用该方法时…局部变量值似乎也被复制到了方法开始时的代码中,所以我不明白你在第二句话中指的是什么样的额外副本。将值从本地复制到闭包类不仅代价高昂,而且在语义上也不正确。闭包的思想是关闭变量,而不是值,因此如果复制值,则不会观察到闭包外部局部变量的更改,也不会观察到闭包中所做的更改。@ShayRojansky:否;局部变量只存在于闭包中,因此没有可复制的内容(参数除外);如果在
if
之后修改
x
,codegen将需要检查闭包是否存在,以找出要写入的内容,这很可怕。“发生了一些意外的分配”-也可能意味着预期是错误的。谢谢,是的,这就是我最终写东西的原因。不过,我最惊讶的是编译器没有为我这样做,我想知道是否有理由阻止它这样做。毕竟,它执行词法分析是为了知道变量定义的最高词法范围(在我的例子中是方法体,因为x是一个参数),它还可以检测变量使用的最高词法范围并在那里分配。@ShayRojansky编译器不需要理由不做某事。事实上,情况正好相反:默认情况下,功能未实现。请看,这种回答并没有多大帮助。我要问的是(显然)是否有充分的理由不实施这种优化,这种优化在某些情况下会减少堆分配,而不会立即产生明显的负面影响。这可能是因为该功能的复杂性无法保证性能的提高,或者是我没有考虑过的其他问题(这就是我询问社区的原因)。如果它不存在的唯一原因是没有人这样做,那么它可能会成为Roslyn的一个好问题。那么为什么不实施它,并在现实世界的程序中衡量性能提升?我相信你会发现1)分析、开发和测试功能比你想象的要复杂;2) 在现实世界中,项目的好处比你想象的要少。好处如此之低的原因之一是,您可以轻松地自己进行优化,而不会显著降低代码的可读性。很多事情使它变得复杂。例如,如果这不是一个
if
语句,而是一个
while
循环(或循环中的if),那么相同的转换可能会使性能更差。您对循环的评论就是我想要的答案,谢谢您。。。