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.
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
).
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.
I'm far from an expert in IL
easiest way to find out how, compile your example without passing 1
to the constructor
this line will go away: IL_0001: ldc.i4.1
which mean, i think, it will not pass an argument to the constructor.
do it again by passing 9 instead of 1, this IL_0001: ldc.i4.1
will be replaced by IL_0001: ldc.i4.s 9
Used the exact same code you provided.. I see following IL, which clearly indicates the overload of Constructor being called -
.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
Note the instance void ConsoleTest.C::.ctor(int32)
And about IL, when in doubt, one can and should always look at the documentation. Since you can Emit IL at runtime, most of the cases, documentation for Emit API will give you sufficient information. For example, in case of NewObj
, it clear states that constructor ConstructorInfo
is required for NewObj
opcode
"Assembly Format newobj ctor
"
"The following Emit method overload can use the newobj opcode: ILGenerator.Emit(OpCode, ConstructorInfo)
"
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.