Now that C# supports named parameters, I was checking to see if it was implemented the same way VB did it, and found that there is a slight difference. Take for example a library function like this:
public static void Foo(string a, string b)
{
Console.WriteLine(string.Format("a: {0}, b: {1}", a, b));
}
In C#, if you call it like this:
Foo(a: "a", b: "b");
The compiler produces the following IL instructions:
.locals init (
[0] string CS$0$0000,
[1] string CS$0$0001)
L_0000: nop
L_0001: ldstr "a"
L_0006: stloc.0
L_0007: ldstr "b"
L_000c: stloc.1
L_000d: ldloc.0
L_000e: ldloc.1
L_000f: call void [TestLibrary]TestLibrary.Test::Foo(string, string)
L_0014: nop
L_0015: ret
Which translates to the following C# code:
string CS$0$0000 = "a";
string CS$0$0001 = "b";
Test.Foo(CS$0$0000, CS$0$0001);
In VB, if you call it like this:
Foo(a:="a", b:="b")
The compiler produces the following IL instructions:
L_0000: nop
L_0001: ldstr "a"
L_0006: ldstr "b"
L_000b:开发者_StackOverflow中文版 call void [TestLibrary]TestLibrary.Test::Foo(string, string)
L_0010: nop
L_0011: nop
L_0012: ret
Which translates to the following VB code:
Foo("a", "b");
The way VB does it requires much fewer instruction calls, so is there any advantage to the way C# implements it? If you don't use the named parameters, C# produces the same thing as VB.
EDIT: Now that we've determined that the extra instructions go away in release mode, is there a particular reason for them to be present in debug mode? VB acts the same in both modes, and C# doesn't insert the extra instructions when calling the method normally without named parameters (including when you use optional parameters).
is there a particular reason for them to be present in debug mode?
The difference is between:
- push a temporary value on the stack, use it, discard it, and
- store a temporary value into a specific stack slot, make a copy of it onto the stack, use it, discard it, but the original copy of the temporary value stays in the stack slot
The visible effect of this difference is that the garbage collector cannot be as aggressive about cleaning up the value. In the first scenario, the value could be collected immediately once the call returns. In the second scenario, the value is only collected after the current method returns (or the slot is re-used).
Making the garbage collector less aggressive often helps in debug scenarios.
The implied question is:
Why the difference between C# and VB?
The C# and VB compilers were written by different people who made different choices about how their respective code generators work.
UPDATE: Re: your comment
In the C# compiler, the unoptimized IL generation is essentially of the same structure as our internal representation of the feature. When we see a named argument:
M(y : Q(), x : R());
where the method is, say
void M(int x, int y) { }
we represent this internally as though you'd written
int ytemp = Q();
int xtemp = R();
M(xtemp, ytemp);
Because we want to preserve the left-to-right evaluation of the side effects of Q and R. This is a reasonable internal representation, and when we codegen it in non-optimized mode, we just codegen the code directly from the internal representation with hardly any modifications.
When we run the optimizer we detect all kinds of stuff -- such as the fact that no one uses those invisible local variables for anything. We can then eliminate the locals from the codegen.
I know very little about the VB internal representation; I haven't worked on the VB compiler since 1995, and I hear that it might have changed just slightly in the last fifteen years. I would imagine that they do something similar, but I don't know the details of how they represent named parameters or how their code generator deals with them.
My point is that this difference does not, to my knowledge, illustrate an important semantic difference. Rather, it illustrates that the nonoptimized build just spits out whatever high-level internal representation we happened to generate that we know has the desired semantics.
The compiler produces the following C# code:
No - the compiler produces IL, which you are then translating to C#. Any resemblence to C# code is purely accidental (and not all the generated IL can be written as C#). The presence of all those "nop" tells me you're in "debug" mode. I would retry in "release" mode - it can make a big difference to these things.
I fired it up in release mode, using:
static void Main()
{
Foo(a: "a", b: "b");
}
Giving:
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 8
L_0000: ldstr "a"
L_0005: ldstr "b"
L_000a: call void ConsoleApplication1.Program::Foo(string, string)
L_000f: ret
}
So identical.
精彩评论