C# MSIL:为什么数组初始值设定项使用dup

C# MSIL:为什么数组初始值设定项使用dup,c#,cil,C#,Cil,我最近在学习MSIL,对阵列有一些困惑: 以下2种方法: private static void FormatTest3() { string s = string.Format("{0}{1}{2}", 1, 2,3); } private static void FormatTest4() { string s = string.Format("{0}{1}{2}{3}", 1, 2,3,4); /* equal to object[] obj =

我最近在学习MSIL,对阵列有一些困惑: 以下2种方法:

private static void FormatTest3()
{
    string s = string.Format("{0}{1}{2}", 1, 2,3);
}

private static void FormatTest4()
{
    string s = string.Format("{0}{1}{2}{3}", 1, 2,3,4);
    /*
    equal to
    object[] obj = new object[4];
    obj[0] = 1;
    obj[1] = 2;
    obj[2] = 3;
    obj[3] = 4;
    string text = string.Format("{0}{1}{2}{3}", obj);
    */
}
以下是IL:

.class private auto ansi '<Module>'
{
} // end of class <Module>

.class private auto ansi beforefieldinit Program
    extends [System.Private.CoreLib]System.Object
{
    // Methods
    .method private hidebysig static 
        void FormatTest3 () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 31 (0x1f)
        .maxstack 4
        .locals init (
            [0] string
        )

        IL_0000: nop
        IL_0001: ldstr "{0}{1}{2}"
        IL_0006: ldc.i4.1
        IL_0007: box [System.Private.CoreLib]System.Int32
        IL_000c: ldc.i4.2
        IL_000d: box [System.Private.CoreLib]System.Int32
        IL_0012: ldc.i4.3
        IL_0013: box [System.Private.CoreLib]System.Int32
        IL_0018: call string [System.Private.CoreLib]System.String::Format(string, object, object, object)
        IL_001d: stloc.0
        IL_001e: ret
    } // end of method Program::FormatTest3

    .method private hidebysig static 
        void FormatTest4 () cil managed 
    {
        // Method begins at RVA 0x207c
        // Code size 55 (0x37)
        .maxstack 5
        .locals init (
            [0] string
        )

        IL_0000: nop
        IL_0001: ldstr "{0}{1}{2}{3}"
        IL_0006: ldc.i4.4
        IL_0007: newarr [System.Private.CoreLib]System.Object
        IL_000c: dup
        IL_000d: ldc.i4.0
        IL_000e: ldc.i4.1
        IL_000f: box [System.Private.CoreLib]System.Int32
        IL_0014: stelem.ref
        IL_0015: dup
        IL_0016: ldc.i4.1
        IL_0017: ldc.i4.2
        IL_0018: box [System.Private.CoreLib]System.Int32
        IL_001d: stelem.ref
        IL_001e: dup
        IL_001f: ldc.i4.2
        IL_0020: ldc.i4.3
        IL_0021: box [System.Private.CoreLib]System.Int32
        IL_0026: stelem.ref
        IL_0027: dup
        IL_0028: ldc.i4.3
        IL_0029: ldc.i4.4
        IL_002a: box [System.Private.CoreLib]System.Int32
        IL_002f: stelem.ref
        IL_0030: call string [System.Private.CoreLib]System.String::Format(string, object[])
        IL_0035: stloc.0
        IL_0036: ret
    } // end of method Program::FormatTest4

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20bf
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
        IL_0006: nop
        IL_0007: ret
    } // end of method Program::.ctor

} // end of class Program
.class私有自动ansi“”
{
}//下课
.class专用自动ansi BEFOREFILDInit程序
扩展[System.Private.CoreLib]System.Object
{
//方法
.method私有隐藏静态
void FormatTest3()cil托管
{
//方法从RVA 0x2050开始
//代码大小31(0x1f)
.maxstack 4
.init(
[0]字符串
)
IL_0000:没有
IL_0001:ldstr“{0}{1}{2}”
IL_0006:ldc.i4.1
IL_0007:box[System.Private.CoreLib]System.Int32
IL_000c:ldc.i4.2
IL_000d:box[System.Private.CoreLib]System.Int32
IL_0012:ldc.i4.3
IL_0013:box[System.Private.CoreLib]System.Int32
IL_0018:调用字符串[System.Private.CoreLib]System.string::Format(字符串,对象,对象)
IL_001d:stloc.0
IL_001e:ret
}//方法结束程序::FormatTest3
.method私有隐藏静态
void FormatTest4()cil托管
{
//方法从RVA 0x207c开始
//代码大小55(0x37)
.maxstack 5
.init(
[0]字符串
)
IL_0000:没有
IL_0001:ldstr“{0}{1}{2}{3}”
IL_0006:ldc.i4.4
IL_0007:newarr[System.Private.CoreLib]System.Object
IL_000c:dup
IL_000d:ldc.i4.0
IL_000e:ldc.i4.1
IL_000f:box[System.Private.CoreLib]System.Int32
IL_0014:stelem.ref
IL_0015:dup
IL_0016:ldc.i4.1
IL_0017:ldc.i4.2
IL_0018:box[System.Private.CoreLib]System.Int32
IL_001d:stelem.ref
IL_001e:dup
IL_001f:ldc.i4.2
IL_0020:ldc.i4.3
IL_0021:box[System.Private.CoreLib]System.Int32
IL_0026:stelem.ref
IL_0027:dup
IL_0028:ldc.i4.3
IL_0029:ldc.i4.4
IL_002a:box[System.Private.CoreLib]System.Int32
IL_002f:stelem.ref
IL_0030:调用字符串[System.Private.CoreLib]System.string::Format(字符串,对象[])
IL_0035:stloc.0
IL_0036:ret
}//方法结束程序::FormatTest4
.method公共隐藏显示特殊名称rtspecialname
实例void.ctor()cil托管
{
//方法从RVA 0x20bf开始
//代码大小8(0x8)
.maxstack 8
IL_0000:ldarg.0
IL_0001:调用实例void[System.Private.CoreLib]System.Object::.ctor()
IL_0006:没有
IL_0007:ret
}//方法程序结束::.ctor
}//课程结束
我的问题是:

  • 为什么带有4个或更多参数的string.Format()使用数组
  • 为什么FormatTest4()的MSIL使用dup(我知道dup做什么)
  • 这是最常见情况下的性能优化。通过对常用数量的参数使用单独的重载,它们不必创建
    params
    array参数,从而节省分配(虽然可能仍然需要装箱,但这比数组便宜)。理论上,0、1、2和3个参数的重载不需要存在,因为采用
    params对象[]
    的方法也可以处理所有这些参数。只是更贵

  • dup
    复制堆栈上的当前项
    stelem.ref
    从堆栈中获取三项:数组、索引和该数组索引的值,并将该值存储在数组中的索引处。这意味着以后数组引用不再在堆栈上。因此,
    dup
    。我们希望将该数组引用保留在堆栈顶部,因为我们需要将其传递给被调用的方法,所以我们创建一个数组,复制它,推送索引和第一项,使用
    stelem.ref
    将该项存储在数组中,并且仍然保留该数组引用,否则该数组引用将消失

    有其他方法可以做到这一点。如果从反编译的C#中复制代码,则最终会得到不同的IL,其中数组引用每次都从局部变量中获取:

    IL_0036: ldc.i4.4
    IL_0037: newarr [System.Private.CoreLib]System.Object
    IL_003c: stloc.1
    IL_003d: ldloc.1
    IL_003e: ldc.i4.0
    IL_003f: ldc.i4.1
    IL_0040: box [System.Private.CoreLib]System.Int32
    IL_0045: stelem.ref
    IL_0046: ldloc.1
    IL_0047: ldc.i4.1
    IL_0048: ldc.i4.2
    IL_0049: box [System.Private.CoreLib]System.Int32
    IL_004e: stelem.ref
    
    我相信这比dup的效率要低,但也许JIT并不在乎这两种方式。真正的反编译C#代码实际上是这样的:

    string text = string.Format("{0}{1}{2}{3}", new object[] { 1, 2, 3, 4 });
    
    这将导致与相同的IL

    string text = string.Format("{0}{1}{2}{3}", 1, 2, 3, 4);
    
  • 这是最常见情况下的性能优化。通过对常用数量的参数使用单独的重载,它们不必创建
    params
    array参数,从而节省分配(虽然可能仍然需要装箱,但这比数组便宜)。理论上,0、1、2和3个参数的重载不需要存在,因为采用
    params对象[]
    的方法也可以处理所有这些参数。只是更贵

  • dup
    复制堆栈上的当前项
    stelem.ref
    从堆栈中获取三项:数组、索引和该数组索引的值,并将该值存储在数组中的索引处。这意味着以后数组引用不再在堆栈上。因此,
    dup
    。我们希望将该数组引用保留在堆栈顶部,因为我们需要将其传递给被调用的方法,所以我们创建一个数组,复制它,推送索引和第一项,使用
    stelem.ref
    将该项存储在数组中,并且仍然保留该数组引用,否则该数组引用将消失

    有其他方法可以做到这一点。如果从反编译的C#中复制代码,则最终会得到不同的IL,其中数组引用每次都从局部变量中获取:

    IL_0036: ldc.i4.4
    IL_0037: newarr [System.Private.CoreLib]System.Object
    IL_003c: stloc.1
    IL_003d: ldloc.1
    IL_003e: ldc.i4.0
    IL_003f: ldc.i4.1
    IL_0040: box [System.Private.CoreLib]System.Int32
    IL_0045: stelem.ref
    IL_0046: ldloc.1
    IL_0047: ldc.i4.1
    IL_0048: ldc.i4.2
    IL_0049: box [System.Private.CoreLib]System.Int32
    IL_004e: stelem.ref
    
    我相信这比dup的效率要低,但也许JIT并不在乎这两种方式。真正的反编译C#代码实际上是这样的:

    string text = string.Format("{0}{1}{2}{3}", new object[] { 1, 2, 3, 4 });
    
    这将导致与相同的IL

    string text = string.Format("{0}{1}{2}{3}", 1, 2, 3, 4);
    
  • 1.在马云看来