Call By Value 與 Call By Reference
學 C# 的第一天起,就深植了這個印像:參數若是數值是 Call By Value,
若是物件則是 Call By Reference。
較精確的說法:
C# 的參數有數種傳遞方式,包含傳值參數 (call by value),傳址參數 (call by reference) 等,基本型態的參數,像是 int, double, char, … 等,預設都使用傳值的方式,而物件形態的參數,像是 StringBuilder,陣列等,預設則是使用傳址的方式。
以上摘錄自: http://cs0.wikidot.com/function
But, 筆者遇到一個看似推翻上述的案例,使用 Lambda Expressions 傳入數值,竟然是以 Call By Reference 方式。
發生不如預期的程式碼如下
在 第 10 行明確傳入 int 數值,但執行結果是等於 si 在迴圈後的值
---> 這看起是 Call By Reference 的結果
經過小調整後,在使用 Lambda Expressions 先 複製 成另一個數值再傳入,執行可以得到預期結果
使用反組譯工具發現,使用 Lambda Expressions 額外多了一個類別 c__DisplayClass2
筆者猜測在執行時期,有可能是透過此類別做為傳遞,
因此仍不違反 "數值是 Call By Value,若是物件則是 Call By Reference" 的說法。
測試案例:下載原始檔 or 直接瀏覽Gist
若是物件則是 Call By Reference。
較精確的說法:
C# 的參數有數種傳遞方式,包含傳值參數 (call by value),傳址參數 (call by reference) 等,基本型態的參數,像是 int, double, char, … 等,預設都使用傳值的方式,而物件形態的參數,像是 StringBuilder,陣列等,預設則是使用傳址的方式。
以上摘錄自: http://cs0.wikidot.com/function
But, 筆者遇到一個看似推翻上述的案例,使用 Lambda Expressions 傳入數值,竟然是以 Call By Reference 方式。
發生不如預期的程式碼如下
public void Test(int numSites) { System.Timers.Timer[] scanLiveTimer = new System.Timers.Timer[numSites]; for (int si = 0; si < numSites; si++) { scanLiveTimer[si] = new System.Timers.Timer(); scanLiveTimer[si].Elapsed += new System.Timers.ElapsedEventHandler((sender, e) => ScanMarketEvent(si)); scanLiveTimer[si].Interval = 500; scanLiveTimer[si].Start(); Console.WriteLine(string.Format("Trigger: {0}", si)); } } void ScanMarketEvent(int si) { // 有誤,讀到的值都一樣 Console.Write(string.Format("{0} ", si)); }
在 第 10 行明確傳入 int 數值,但執行結果是等於 si 在迴圈後的值
---> 這看起是 Call By Reference 的結果
經過小調整後,在使用 Lambda Expressions 先 複製 成另一個數值再傳入,執行可以得到預期結果
public void Test(int numSites) { System.Timers.Timer[] scanLiveTimer = new System.Timers.Timer[numSites]; for (int si = 0; si < numSites; si++) { scanLiveTimer[si] = new System.Timers.Timer(); // 複製變數,再傳入 lambda expression int x = si; scanLiveTimer[si].Elapsed += new System.Timers.ElapsedEventHandler((sender, e) => ScanMarketEvent(x)); scanLiveTimer[si].Interval = 500; scanLiveTimer[si].Start(); Console.WriteLine(string.Format("Trigger: {0}", si)); } } void ScanMarketEvent(int si) { // 可以讀到正確的值 Console.Write(string.Format("{0} ", si)); }
使用反組譯工具發現,使用 Lambda Expressions 額外多了一個類別 c__DisplayClass2
筆者猜測在執行時期,有可能是透過此類別做為傳遞,
因此仍不違反 "數值是 Call By Value,若是物件則是 Call By Reference" 的說法。
測試案例:下載原始檔 or 直接瀏覽Gist