VS 2010から2015にアップグレードしました。新しい null-conditional operator が好きです。これはnull-propagationとも呼ばれます。これにより、コードを簡素化できます。次に例を示します。
string firstCustomerName = customers?[0].Name; // null if customers or the first customer is null
もう1つ:
int? count = customers?[0]?.Orders?.Count(); // null if customers, the first customer, or Orders is null
これは、Nullable<int>
がint
を返し、有効なカウントとそれ以前のnulls
を区別する場合でも、Enumerable.Count
を返します。これは非常に直感的で非常に便利です。
しかし、なぜこれが期待どおりにコンパイルおよび動作するのですか(false
を返します):
string text = null;
bool contains = text?.IndexOf("Foo", StringComparison.CurrentCultureIgnoreCase) >= 0;
bool?
(返されません)を返すか、コンパイルしない必要があります。
あなたが実際に持っているのは
string text = null;
int? index = text?.IndexOf("Foo", StringComparison.CurrentCultureIgnoreCase);
bool contains = index >= 0;
int? >= int
は完全に合法です。
分割された理由は 演算子のドキュメント 状態 "条件付きメンバーアクセスとインデックス操作のチェーン内の1つの操作がnullを返す場合、チェーンの残りの部分は実行は停止します。式の優先順位が低い他の操作は続行されます。 "つまり、.?
は、「値を作成」する前に、同じ優先順位以上のもののみを評価します。
演算子の優先順位 を見ると、「関係演算子と型テスト演算子」がリストのはるかに低いため、>=
が適用される前に値が作成されます。
UPDATE:コメントで取り上げられたので、ここに>=
と他の演算子が処理するときの動作に関するC#5仕様のセクションがありますnull許容値を使用します。 C#6のドキュメントが見つかりませんでした。
7.3.7リフトされたオペレーター
リフト演算子を使用すると、null許容でない値型を操作する事前定義およびユーザー定義の演算子を、これらの型のnull許容形式でも使用できます。リフト演算子は、以下で説明するように、特定の要件を満たす事前定義およびユーザー定義の演算子から構成されます。
単項演算子の場合
+ ++ - -- ! ~
オペランドと結果の型が両方ともNULL可能でない値型である場合、リフトされた形式の演算子が存在します。持ち上げられたフォームは、単一の?オペランドと結果タイプの修飾子。オペランドがNULLの場合、リフトされた演算子はNULL値を生成します。それ以外の場合、リフトされた演算子はオペランドをアンラップし、基になる演算子を適用して、結果をラップします。
二項演算子の場合
+ - * / % & | ^ << >>
オペランドと結果の型がすべてnull許容でない値型である場合、リフトされた形式の演算子が存在します。持ち上げられたフォームは、単一の?各オペランドと結果タイプの修飾子。リフトされた演算子は、一方または両方のオペランドがnullの場合、null値を生成します(§7.11.3で説明されているように、bool?タイプの&および|演算子は例外です)。それ以外の場合、リフトされた演算子はオペランドをアンラップし、基になる演算子を適用して、結果をラップします。
等式演算子の場合
== !=
オペランドタイプが両方ともnull許容でない値タイプであり、結果タイプがブール値である場合、演算子のリフト形式が存在します。持ち上げられたフォームは、単一の?各オペランドタイプの修飾子。解除された演算子は、2つのnull値が等しいと見なし、null値がnull以外の値と等しくないと見なします。両方のオペランドがnull以外の場合、持ち上げられた演算子はオペランドをアンラップし、基になる演算子を適用してブール結果を生成します。
関係演算子の場合
< > <= >=
オペランドタイプが両方ともnull許容でない値タイプであり、結果タイプがブール値である場合、演算子のリフト形式が存在します。持ち上げられたフォームは、単一の?各オペランドタイプの修飾子。リフトされた演算子は、一方または両方のオペランドがnullの場合、値falseを生成します。それ以外の場合、持ち上げられた演算子はオペランドをアンラップし、基になる演算子を適用してブール結果を生成します。
このコードを使用し、x
にカーソルを合わせると、x
が_int?
_であることがわかります。
_var x = text?.IndexOf("Foo", StringComparison.CurrentCultureIgnoreCase);
bool contains = x >= 0;
_
したがって、入力はまだ正しいです。
次に、_x >= 0
_を調べます。これは_int? >= int
_です。どうやら、null許容構造体と非null許容構造体の間に演算子があります。それが機能する理由です。
ILを見ると、実際にはHasValue
とGetValueOrDefault()
を呼び出していることがわかります。これを実行しているオペレーターがいると思いますが、参照ソースで見つからなかったため、CLRまたはコンパイラーにあるはずです。
_instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault()
...
instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
_