C#IL - 调用构造函数

时间:2014-12-21 00:37:13

标签: c# .net cil

我正在学习C#IL简单的例子并且无法理解某些东西。我有一个非常简单的程序:

void Main()
{
   C c = new C(1);
}
class C
{
   public C(){}
   public C(int i){}
}

有CIL:

IL_0001:  ldc.i4.1    
IL_0002:  newobj      UserQuery+C..ctor
IL_0007:  stloc.0     // c

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  nop         
IL_0009:  ret         

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  nop         
IL_0009:  ret  

我不明白,虚拟机将如何区分应该调用哪个构造函数。有两个相同的标签,唯一的区别似乎是推动主要论证。调用构造函数时有更深层次的东西吗?也许编译器提供了一些元数据来区分应该调用哪一个?

所以让我们假设:

void Main()
{
  C c = new C(1);
}
class C
{
  public C(){}
  public C(int i){ i += 1;}
}

IL_0001:  ldc.i4.1    
IL_0002:  newobj      UserQuery+C..ctor
IL_0007:  stloc.0     // c

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  nop         
IL_0009:  ret         

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  ldarg.1     
IL_0009:  ldc.i4.1    
IL_000A:  add         
IL_000B:  starg.s     01 
IL_000D:  nop         
IL_000E:  ret   

现在,如何区分哪一个调用,在标签级别我们无法区分它。

4 个答案:

答案 0 :(得分:5)

实际代码标识了MethodToken要调用的构造函数。这些构造函数对于每个重载都是唯一的。

您的反汇编程序具有不适当的令牌到字符串转换,它只提供名称,该名称不是唯一的,并且无法组装。相反,ildasm将令牌转换为完整签名,该签名能够往返于工作程序集(使用ilasm)。

答案 1 :(得分:4)

我去做实验......

我使用了以下代码:

class Program
{
    static void Main()
    {
        CallConstructorA();
        CallConstructorB();
    }

    static void CallConstructorA()
    {
        GC.KeepAlive(new C());
    }

    static void CallConstructorB()
    {
        GC.KeepAlive(new C(1));
    }
}

class C
{
    public C() { }
    public C(int i)
    {
        GC.KeepAlive(i);
    }
}

以下是MSIL与Telerik JustDecompile合作的课程:

.class private auto ansi beforefieldinit Test.Program
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
    {
        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    }

    .method private hidebysig static void CallConstructorA () cil managed 
    {
        IL_0000: nop
        IL_0001: newobj instance void Test.C::.ctor()
        IL_0006: call void [mscorlib]System.GC::KeepAlive(object)
        IL_000b: nop
        IL_000c: ret
    }

    .method private hidebysig static void CallConstructorB () cil managed 
    {
        IL_0000: nop
        IL_0001: ldc.i4.1
        IL_0002: newobj instance void Test.C::.ctor(int32)
        IL_0007: call void [mscorlib]System.GC::KeepAlive(object)
        IL_000c: nop
        IL_000d: ret
    }

    .method private hidebysig static void Main () cil managed 
    {
        .entrypoint
        IL_0000: nop
        IL_0001: call void Test.Program::CallConstructorA()
        IL_0006: nop
        IL_0007: call void Test.Program::CallConstructorB()
        IL_000c: nop
        IL_000d: ret
    }
}

所以你可以看到电话不同......

第一个说:

        IL_0001: newobj instance void Test.C::.ctor()

第二个说:

        IL_0002: newobj instance void Test.C::.ctor(int32)

所以,我认为你的反编译器没有显示中间代码的所有细节。事实上,我确实在LINQPad中尝试了与上面类似的代码,并且这两个调用看起来很相似。


有关如何以二进制方式完成注释的详细信息......老实说,我不知道。

答案 2 :(得分:2)

我远非IL的专家

找出方法的最简单方法,编译您的示例而不将1传递给构造函数

这一行将消失:IL_0001: ldc.i4.1这意味着,我认为它不会将参数传递给构造函数。

通过传递9而不是1来再次执行此操作,此IL_0001: ldc.i4.1将替换为IL_0001: ldc.i4.s 9

答案 3 :(得分:2)

使用你提供的完全相同的代码..我看到了IL,它清楚地表明了被调用的构造函数的重载 -

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       9 (0x9)
  .maxstack  1
  .locals init ([0] class ConsoleTest.C c)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  newobj     instance void ConsoleTest.C::.ctor(int32)
  IL_0007:  stloc.0
  IL_0008:  ret
} // end of method Program::Main

请注意 instance void ConsoleTest.C::.ctor(int32)

关于IL,如果有疑问,可以而且应该始终查看文档。 由于您可以在运行时发送IL,因此大多数情况下,Emit API的文档将为您提供足够的信息。例如,如果是NewObj,则明确说明ConstructorInfo操作码需要构造函数NewObj

“汇编格式newobj ctor

“以下Emit方法重载可以使用newobj操作码:ILGenerator.Emit(OpCode, ConstructorInfo)