null合体演算子
null合体演算子は左側オペランドがnullでない場合は左側オペランドを返し、nullの場合は右側オペランドを返します。参照型のほか、Null許容型(Nullable)にも適用可能です。C# 2.0で追加されました。
x ?? alt
三項演算子で書くと以下のようになります。
x != null ? x : alt
null条件演算子
null条件演算子はオペランドがnullでない場合はメンバーアクセス(.
もしくは[]
)を実行します。参照型のほか、Null許容型(Nullable)にも適用可能です。C# 6.0で追加されました。
x?.SomeMethod();
if文で書くと以下のようになります。
if (x != null) { x.SomeMethod(); }
もしここでSomeMethod()が値型T
の戻り値を持つ場合、x?.SomeMethod()
の評価結果はNull許容型T?
となります。三項演算子で書くと以下のようになります。
x != null ? x.SomeMethod() : default(T?)
応用
これらの演算子を応用して、「引数がnullでない場合はToString()を呼び出し、nullの場合は代替のリテラル文字列"N/A"を返す」という処理をスマートに書いてみます。
// (1) public static string ConvertToNAIfNull<T>(T x) { return x?.ToString() ?? "N/A"; }
もしこれらの演算子を使わずに書くとすれば、以下のようになります。
// (2) public static string ConvertToNAIfNull<T>(T x) { return x != null ? x.ToString() : "N/A"; }
いずれにせよ1行で書けますが、(2)はx
が2回出現して冗長なので、特にメソッドではなくインラインで書く際に不便そうですね。厳密に言うと、(1)と(2)は完全互換ではありませんが、通例ToString()は空文字列""を返却することはあってもnullを返却することはないので実用上は問題ないはずです。
ここで興味深いのが、(1)のジェネリクスには参照型やNull許容型だけでなく、通常の値型を渡しても実体化できるということです。csc 2015 (Visual C# 2015) でもgmcs 4.6.2でも動作します。
Console.WriteLine(ConvertToNAIfNull(new int?(10))); Console.WriteLine(ConvertToNAIfNull(default(int?))); Console.WriteLine(ConvertToNAIfNull(10));
値型に対する?.
演算子呼び出しは無効なので、一見コンパイルエラーになってしまう気がしますが、(1)のジェネリクスはC#コンパイラーが内部で以下のように展開するせいで、値型を渡してもコンパイルエラーにはならず実体化できる、というからくりになっているようです。
// (3) public static string ConvertToNAIfNull<T>(T x) { if (x != null) { var t = x.ToString(); if (t != null) { return t; } } return "N/A"; }
値型に対してはx != null
はコンパイルエラーにはならず常に真なので、(2)や(3)のジェネリクスに値型を渡せるのは当然です。いずれにせよ、ConvertToNAIfNull()に値型を渡すと冗長なメソッドになってしまいますが。