2010-02-25 129 views
18

既然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#不會插入額外的指令。

+0

開關順序(:= 「B」,A:B = 「一個」)以查看差(提示:副作用順序) – adrianm 2010-02-25 05:11:33

+0

什麼是等效VB代碼? – 2010-02-25 05:15:52

+0

@adrianm一個有趣的想法,但我剛剛嘗試過(出於好奇),併產生與'Foo(a:「a」,b:「b」)相同的IL;' – 2010-02-25 05:18:28

回答

13

是否有特別的原因讓它們出現在調試模式下?

的區別在於之間:

  • 推棧上的一個臨時值,使用它,將其丟棄,並且
  • 存儲臨時值到特定的堆棧槽,使得它的一個副本將其丟棄,但臨時值的原始副本保留在堆棧槽中

該區別的可見效果是垃圾回收器不能像清理一樣積極價值。在第一種情況下,一旦呼叫返回,可以立即收集該值。在第二種情況下,僅在當前方法返回(或重新使用該插槽)後收集該值。

使垃圾回收器不那麼強大通常有助於調試場景。

隱含的問題是:

爲什麼C#和VB之間的區別?

C#和VB編譯器是由不同的人編寫的,他們對各自的代碼生成器的工作方式做出了不同的選擇。

UPDATE:回覆:您的評論

在C#編譯器,沒有優化的IL代基本上是相同的結構,我們的特點的內部表示的。當我們看到一個命名參數:

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

其中,該方法是,說

void M(int x, int y) { } 

我們代表這個內部,就像你寫

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

因爲我們要保護從Q到R的副作用從左到右評估。這是一個合理的內部表示,當我們以非優化模式對其進行編碼時,我們直接從內部代表幾乎沒有任何修改。

當我們運行優化我們發現各種東西 - 比如,沒有人使用這些看不見的局部變量什麼的事實。然後我們可以從代碼生成器中刪除當地人。

我對VB的內部表示知之甚少;自1995年以來,我一直沒有在VB編譯器上工作過,而且我聽說在過去的十五年裏它可能已經發生了輕微的變化。我會想象他們做了類似的事情,但我不知道他們如何表示命名參數的細節,或者他們的代碼生成器如何處理它們。

我的觀點是,就我所知,這種差異並不能說明重要的語義差異。相反,它表明,非優化構建只是吐出我們碰巧產生的任何高級內部表示,我們知道它具有所需的語義。上的參數,富

+1

@Eric這是一個很好的解釋,謝謝!至於隱含的問題,我認爲它更多的是「爲什麼他們做出這些選擇」,因爲我認爲這樣做有充分的理由。 – 2010-02-25 16:28:49

+0

+1強調從左到右的評估,你們似乎在C#中虔誠地遵循它來保持一致(在另一個答案中也指您的F()+ G()* H()示例) – 2010-02-26 07:11:32

5

編譯器產生以下的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 
} 

所以相同。

+0

你說得對,我有語言倒退......好。 我在釋放模式(我以爲我曾嘗試過,但它似乎我沒有)再次嘗試它。 C#IL指令與該模式下的VB指令相匹配。 這仍然讓我想知道在調試模式下產生額外指令的原因是什麼,因爲VB似乎在兩種模式下都是這樣做的,但它很好地知道它在發佈模式中不是問題。 – 2010-02-25 05:20:30

+0

@gshackles - 我猜想它在內部使用最簡單的解釋,*在翻譯階段(即問題中的版本)保證正確性*,然後依靠編譯器後期(可選)階段對其進行優化。非常直接的代碼翻譯使調試器更簡單,更可靠。 – 2010-02-25 05:32:29

相關問題