同事回報某段C#程式發現Bug:
int lastQty = 100;
int? soldQty = null;
int leaveQty = lastQty - soldQty ?? 0;
soldQty 由其他系統傳入可能為 null,原本我的想法是遇到 soldQty==null 就視為0,此時 leaveQty 應等於 lastQty,但以上程式執行結果與預期不同,leaveQty == 0!
這枚Bug隱藏了兩項疑問:
- ?? 運算子(Operator)與加減乘除誰先誰後?
- int與null加減乘除時結果會是什麼?
同事與我都不知道答案,爬文後才把這段空白知識補齊。
運算子優先順序大全
找到一篇超完整的C#運算子列表,運算子優先順序依序為:
- 主要運算子(Primary Operators)
x.y, x?.y, f(x), a[x], a?[x], x++, x—, new, typeof, checked, unchecked, default(T), delegate, sizeof, –> - 一元運算子(Unary Operators)
+x, -x, !x, ~x, ++x, -x, (T)x, await, &x, *x - 乘法類運算子(Multiplicative Operators)
x*y, x/y, x%y - 加法類運算子(Additive Operators)
x+y, x-y - 移位運算子(Shift Operators)
x<<y, x>>y - 關係和類型測試運算子(Relational and Type-testing Operators)
x<y, x<y, x<=y, x>=y, is, as - 等號比較運算子(Equality Operators)
x==y, x!=y - 邏輯AND運算子(Logical AND Operator)
x&y - 邏輯XOR運算子(Logical XOR Operator)
x^y - 邏輯OR運算子(Logical OR Operator)
x|y - 條件式AND運算子(Conditional AND Operator)
x&&y - 條件式OR運算子(Conditional OR Operator)
x||y - Null聯合運算子(Null-coalescing Operator)
x??y - 條件運算子(Conditional Operator)
t?x:y - 指派及Lambda運算子(Assignment and Lambda Operators)
x=y, x+=y, x-=y, x*=y, x/=y, x%=y, x&=y, x|=y, x^=y,x<<=y,x>>=y,=> - 算術溢位(Arthmetic Overflow)
??排名第13,故 lastQty - soldQty ?? 0 會先計算 100-null 再取??0,由結果反推 100-null 的結果為null。
null與數字如何運算
依MSDN文件:
The predefined unary and binary operators and any user-defined operators that exist for value types may also be used by nullable types. These operators produce a null value if the operands are null; otherwise, the operator uses the contained value to calculate the result.
當運算元(即運算子範圍中的x或y)型別為Nullable<ValueType>且其值為null,以一元或二元運算子計算結果恆為null。
When performing comparisons with nullable types, if one of the nullable types is null, the comparison is always evaluated to be false. It is therefore important not to assume that because a comparison is false, the opposite case is true
對null進行大小比較時,其結果恆為false,例如:int? value = null,則條件式 value > 0、 value < 0、value == 0 都不成立。
此一特性跟 DB 的 null 幾乎一模一樣,未來應用時要留意。(延伸閱讀:詭異的NOT IN查詢,原來是NULL搞鬼)
又上了一課~
【2016-07-14補充】
在專頁陸續接獲網友Shengkai及比爾叔補充好建議一則:面對此類情境索性加上括號寫成:leavQty – (soldQty ?? 0),一來語意清楚方便後續維護者理解,二來免除記錯運算子優先順序的風險,決定未來都依此辦理。