++i vs i++

i++ 與 ++i 如果在不影響結果的情況下使用,有可能是在 for 迴圈的遞增條件下,或是只是很單純的呼叫,可能因為編譯器最佳化的關係,兩者其實沒有什麼不同。


像是下面這樣單獨呼叫 ++i。

1
2
var i = 0;
++i;


或是像是下面這樣單獨呼叫 i++。

1
2
var i = 0;
i++;


其對應的 IL 都是一樣的:

1
2
3
4
5
6
7
8
IL_0000:  nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: stloc.0
IL_0007: ret


但在影響結果的情況下使用(像是處理完做了賦值的動作),除了結果不同外,還會多耗用一個堆疊位置,這邊以一個簡單的例子來看:

1
2
var i = 0;
var a = i++;


對應的 IL 如下:

1
2
3
4
5
6
7
8
9
10
IL_0000:  nop         
IL_0001: ldc.i4.0
IL_0002: stloc.0 // i
IL_0003: ldloc.0 // i
IL_0004: dup
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stloc.0 // i
IL_0008: stloc.1 // a
IL_0009: ret


整個運作是會先將堆疊內放置一個 0,然後將堆疊內的 0 取出放到區域變數 i,再將 i 的值放到堆疊內,這時堆疊內的值還是 0。


然後將堆疊內的 0 複製一份放至堆疊,這時堆疊內的值為 00。


接著將 1 放入堆疊,堆疊內的值變為 001。


再來從堆疊內取出兩個值相加後放回堆疊,堆疊內的值變為 01。


最後將堆疊內的值取出依序塞給區域變數 i 與 a。


可以看到這樣的處理最多需要的堆疊大小為 3。


再來來看 ++i,一樣用個間單的例子來看:

1
2
var i = 0;
var a = ++i;


其對應的 IL 如下:

1
2
3
4
5
6
7
8
9
10
IL_0000:  nop         
IL_0001: ldc.i4.0
IL_0002: stloc.0 // i
IL_0003: ldloc.0 // i
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: dup
IL_0007: stloc.0 // i
IL_0008: stloc.1 // a
IL_0009: ret


整個運作是會先將堆疊內放置一個 0,然後將堆疊內的 0 取出放到區域變數 i,再將 i 的值
放到堆疊內,這時堆疊內的值還是 0。


接著會在堆疊放置一個 1,這時堆疊內的值為 01。


再來從堆疊內取出兩個值相加後放回堆疊,堆疊內的值變為 1。


然後複製堆疊頂端的值,此時堆疊內的值為 11。


最後將堆疊內的值取出依序塞給區域變數 i 與 a。


這樣的處理最多需要的堆疊大小為 2。


所以在這種情境下,能避開 i++ 就避開,像是下面這樣的程式:

1
2
3
4
5
6
7
var idx = 0
var data = new int[] { 1, 2, 3, 4 };

var data1 = data[idx++];
var data2 = data[idx++];
var data3 = data[idx++];
var data4 = data[idx++];


就可以將它改寫成下面這樣:

1
2
3
4
5
6
7
var idx = -1;
var data = new int[] { 1, 2, 3, 4 };

var data1 = data[++idx];
var data2 = data[++idx];
var data3 = data[++idx];
var data4 = data[++idx];


或是將它改成不影響結果的那種寫法也可以,