C#和VB如何处理命名参数之间的差异?

现在C#支持命名参数,我正在检查它是否以与VB相同的方式实现,并发现存在细微差别。 以这样的库函数为例:

public static void Foo(string a, string b) { Console.WriteLine(string.Format("a: {0}, b: {1}", a, b)); } 

在C#中,如果你这样称呼它:

 Foo(a: "a", b: "b"); 

编译器生成以下IL指令:

 .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 

这转换为以下C#代码:

 string CS$0$0000 = "a"; string CS$0$0001 = "b"; Test.Foo(CS$0$0000, CS$0$0001); 

在VB中,如果你这样称呼它:

 Foo(a:="a", b:="b") 

编译器生成以下IL指令:

 L_0000: nop L_0001: ldstr "a" L_0006: ldstr "b" L_000b: call void [TestLibrary]TestLibrary.Test::Foo(string, string) L_0010: nop L_0011: nop L_0012: ret 

这转换为以下VB代码:

 Foo("a", "b"); 

VB的方式需要更少的指令调用,因此C#实现它的方式有什么优势吗? 如果不使用命名参数,C#会生成与VB相同的内容。


编辑 :现在我们已经确定额外的指令在发布模式下消失了,是否有一个特殊的原因让它们出现在调试模式中? VB在两种模式下的作用相同,并且在没有命名参数的情况下正常调用方法时C#不会插入额外的指令(包括使用可选参数时)。

是否有一个特殊的原因让他们出现在调试模式?

区别在于:

  • 在堆栈上推送一个临时值,使用它,丢弃它,然后
  • 将临时值存储到特定的堆栈槽中,将其复制到堆栈中,使用它,丢弃它,但临时值的原始副本保留在堆栈槽中

这种差异的明显效果是垃圾收集器不能像清理值那样积极。 在第一种情况下,一旦呼叫返回,就可以立即收集该值。 在第二种情况下,仅在当前方法返回(或重新使用插槽)之后收集该值。

使垃圾收集器不那么激进通常有助于调试方案。

隐含的问题是:

为什么C#和VB之间存在差异?

C#和VB编译器是由不同的人编写的,他们对各自的代码生成器的工作方式做出了不同的选择。

更新:回复:你的评论

在C#编译器中,未优化的IL生成与我们内部的特征表示基本上具有相同的结构。 当我们看到一个命名参数时:

 M(y : Q(), x : R()); 

比如,方法在哪里

 void M(int x, int y) { } 

我们在内部表示,就像你写的那样

 int ytemp = Q(); int xtemp = R(); M(xtemp, ytemp); 

因为我们想要保持Q和R的副作用的从左到右的评估。这是一个合理的内部表示,当我们以非优化模式编码它时,我们只是直接从内部表示编码代码几乎没有修改。

当我们运行优化器时,我们会检测所有类型的东西 – 比如没有人使用那些不可见的局部变量。 然后我们可以从codegen中消除本地人。

我对VB内部表示知之甚少; 我从1995年开始就没有使用VB编译器,我听说在过去的十五年里它可能只是略有改变。 我想他们会做类似的事情,但我不知道他们如何代表命名参数或他们的代码生成器如何处理它们的细节。

我的观点是,据我所知,这种差异并没有说明一个重要的语义差异。 相反,它说明了非优化构建只是吐出我们碰巧生成的任何高级内部表示,我们知道它具有所需的语义。

编译器生成以下C#代码:

否 – 编译器生成IL,然后将其转换为C#。 与C#代码的任何相似之处纯属偶然(并非所有生成的IL 可以写成C#)。 所有那些“nop”的存在告诉我你处于“调试”模式。 我会在“发布”模式下重试 – 它可以对这些事情产生重大影响。

我在发布模式下将其启动,使用:

 static void Main() { Foo(a: "a", b: "b"); } 

赠送:

 .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 } 

如此相同。