這篇繼續聊聊 ”引數“的一些話題,我們知道引數大概有”預設引數“,”可選引數“,”ref引數“,”out引數“以及”可變引數“。
下面提幾個小問題,可能在面試中會被問到。
Q:請問我按照如下方式傳遞引數的時候,最後的m等於多少?
1 static void Main(string[] args) 2 { 3 int k = 0; 4 5 Run(k++, k++, k++); 6 } 7 8 static void Run(int i, int j, int m) 9 { 10 //最後的m等於多少 11 Console.WriteLine(m); 12 }
A: 不管這個問題算不算小兒科,既然被問到了,並且又是在引數這個博文裡面,當然要麼直接加等於2,要麼就是0,如果你在
區域性程式碼區域直接寫k++,那麼毫無疑問的就是k=k;k=k+1,也就是先賦值再自增,那如果作為引數的話,還是一樣嗎?
答案當然就在IL裡面。
從IL上我們看的很清楚,即使++操作是作為引數的形式,也是依次執行了三個add,然後add完之後再call我們的run方法。
最後得到結果毫無疑問就是2了。
Q:我知道預設引數是C#4.0的新特性,難道它又是一塊語法糖嗎?
1 static void Run(int i = 4) 2 { 3 4 }
A: 可以這麼說的,我們知道C#有一個限制,就是預設值必須是編譯時就能確定的常量值,既然是常量值,那麼這個值就一定
會嵌入到程式集的後設資料中,老規矩,繼續看下生成的IL程式碼。
如果你仔細觀察,你會發現有兩個不同的地方。
①:引數列表中的opt,這個引數其實就是編譯器給該引數打上了OptionalAtrribute標記,既然是特性,它也會嵌入到程式集
的後設資料中,下面看下它的原始碼會發現沒什麼有價值的地方,就是標記這個引數是不是可選的。
② 我們會發現有一個param引數,其實這個引數就是編譯器給引數打上的一個預設值的標記,繼續看下原始碼。
這裡我們發現有一個建構函式,需要傳遞一個預設值,而這個預設值取自我們定義的常量值,也就是4.
所以綜合來說,確實是一塊語法糖,其實真實的程式碼應該是這樣,只是賦值操作給了編譯器。
1 static void Run(int i) 2 { 3 i = 4; 4 5 //.... 6 }
Q:我知道Param有些場景會比int[]更有語意,比如下面程式碼,能說明下它的實現原理嗎?
1 public class Program 2 { 3 static void Main(string[] args) 4 { 5 Add(new int[3] { 1, 2, 3 }); 6 7 //是不是有更好的語意 8 AddRange(1, 2, 3); 9 } 10 11 /// <summary> 12 /// 這裡必須傳遞int[]陣列 13 /// </summary> 14 /// <param name="nums"></param> 15 static void Add(int[] nums) 16 { 17 18 } 19 20 /// <summary> 21 /// 這裡直接傳遞資料元素值即可,不需要int[] 22 /// </summary> 23 /// <param name="nums"></param> 24 static void AddRange(params int[] nums) 25 { 26 27 } 28 }
A: 確實在add的場景下語意大增了不少,同時也讓我少寫了一些程式碼,那麼到底param是如果做到的呢?我們繼續
看下IL程式碼。
從IL中上可以看到,其實所謂的呼叫方,即:AddRange(1, 2, 3); 它在呼叫之前已經new了一個arr,並且將1,2,3
加入到arr中去了,然後再呼叫AddRange陣列的,所以可以看出,又是一枚語法糖。
好了,大概就這樣了,夜深了,睡覺了。