我正在学习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
现在,如何区分哪一个调用,在标签级别我们无法区分它。
答案 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)
”