为什么代码生成名为 <>c__DisplayClass1 的 MSIL 类

本文关键字:DisplayClass1 MSIL 代码生成 为什么 | 更新日期: 2023-09-27 17:56:47

我有这个代码,

private bool MatchingBreak(IEnumerable<CarriagewaySummary> breaks, int startMetres, int divisionPosition)
    {
        CarriagewaySummary matchingBreak = breaks.Where(x =>
        {
            return x.StartMetres == startMetres && x.EndMetres == divisionPosition;
        }).SingleOrDefault();
        return matchingBreak != null;
    }

为什么这会在 MSIL 中生成一个名为 <>c__DisplayClass1 的嵌套类?

.class nested private auto ansi sealed beforefieldinit <>c__DisplayClass1
extends object
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
    01 00 00 00
)
// Fields
.field public int32 startMetres
.field public int32 divisionPosition
// Methods
.method public hidebysig specialname rtspecialname 
    instance void .ctor () cil managed 
{
    // Method begins at RVA 0x56fb
    // Code size 7 (0x7)
    .maxstack 8
    IL_0000: ldarg.0
    IL_0001: call instance void object::.ctor()
    IL_0006: ret
} // End of method <>c__DisplayClass1..ctor
.method public hidebysig 
    instance bool <MatchingBreak>b__0 (
        class TreatmentLengthDynamicSegmentation.Domain.CarriagewaySummary x
    ) cil managed 
{
    // Method begins at RVA 0x5704
    // Code size 37 (0x25)
    .maxstack 2
    .locals init (
        [0] bool 
    )
    IL_0000: nop
    IL_0001: ldarg.1
    IL_0002: callvirt instance int32 TreatmentLengthDynamicSegmentation.Domain.CarriagewaySummary::get_StartMetres()
    IL_0007: ldarg.0
    IL_0008: ldfld int32 class TreatmentLengthDynamicSegmentation.ScriptHelpers.DivisionManager/<>c__DisplayClass1::startMetres
    IL_000d: bne.un.s IL_001f
    IL_000f: ldarg.1
    IL_0010: callvirt instance int32 TreatmentLengthDynamicSegmentation.Domain.CarriagewaySummary::get_EndMetres()
    IL_0015: ldarg.0
    IL_0016: ldfld int32 class TreatmentLengthDynamicSegmentation.ScriptHelpers.DivisionManager/<>c__DisplayClass1::divisionPosition
    IL_001b: ceq
    IL_001d: br.s IL_0020
    IL_001f: ldc.i4.0
    IL_0020: stloc.0
    IL_0021: br.s IL_0023
    IL_0023: ldloc.0
    IL_0024: ret
} // End of method <>c__DisplayClass1.<MatchingBreak>b__0
} // End of class TreatmentLengthDynamicSegmentation.ScriptHelpers.DivisionManager/<>c__DisplayClass1

生成的代码干扰了Nitriq代码分析,所以我想了解它为什么存在。

为什么代码生成名为 <>c__DisplayClass1 的 MSIL 类

如果您在 lambda 中使用局部变量,则需要在堆上。lambda 可以在创建它的函数退出后使用。当函数退出时,普通局部变量(存在于堆栈/寄存器上)变得无效,因此不能在此处使用它们。

因此,C# 编译器创建一个类来保存捕获的局部变量。这就是你看到的那个。

请注意,C# 捕获的是实际变量,而不是其当前值。所以从概念上讲,它是通过引用捕获的。捕获的语义意味着编译器需要为每个范围创建一个容器对象。

http://csharpindepth.com/Articles/Chapter5/Closures.aspx


在您的代码中

x =>
    {
        return x.StartMetres == startMetres && x.EndMetres == divisionPosition;
    }

lambda使用startMetresdivisionPosition,所以它们都被捕获并被放入嵌套类中。

您正在将 lambda 用于 Where 扩展方法,该方法要求编译器在 lambda 捕获外部变量时生成类。在这种情况下,将同时捕获startMetresdivisionPosition参数。

您会看到编译器生成的类,以便保存捕获的变量。