I'm studying C# IL simple example and can't understand something. I've got very simple program:
void Main()
{
C c = new C(1);
}
class C
{
public C(){}
public C(int i){}
}
there is 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
I don't understand, how virtual machine will distinguish which one constructor should be call. There is two same labels and the only difference seems to be in pushing argument in main. Is there something more deeper when calling constructor? Maybe compiler are providing some meta-data to distinguish which one should be called?
So let's assume this:
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
Now, how to distinguish which one to call, at the label-level we cannot distinguish it.
I went to make the experiment...
I used the following code:
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);
}
}
The following is MSIL got with Telerik JustDecompile for the class Program:
.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
}
}
So you can see that the calls are different...
The first one says:
IL_0001: newobj instance void Test.C::.ctor()
The second says:
IL_0002: newobj instance void Test.C::.ctor(int32)
So, I'm gessing it is your decompiler that is not showing all the details of the intermediate code. In fact I did try similar code to the one above in LINQPad and there both call looks alike.
For the detail of how the annotation is done in binary... honestly I don't know.
The actual code identifies the constructor to be called by a MethodToken
These are unique for each overload.
Your disassembler has an inadequate token-to-string conversion which is giving you only the name, which isn't unique, and cannot be assembled. In constrast, ildasm
converts the token into a full signature which is able to roundtrip back to a working assembly (using ilasm
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With