イマサラですが、三項演算子 (条件演算子) ってコンパクトに書けていいですよね。利用条件というか制約はありますが、オブジェクト初期化子などif文が書けないようなところにも書くことができて大変便利です。三項演算子は書き方がどーのこーのということが言われることもありますが、そのあたりはコードの見栄えの問題だと思っているので、僕は綺麗に見えればOK派です (派閥争いなどをするつもりはないですが...)。
そんなこんなで(?)、今回はNull許容型への代入を目的に使った場合のお話です。
悔しいコンパイルエラー
例えば、次のようなコードについて考えてみましょう。これはパッと見コンパイルが通りそうです。そして変数idの型はNullable<int>になると予想されます。
static void Main() { var obj = new { Id = 1 }; var id = obj == null ? null : obj.Id; }
けれど、実際にはこれは次のようなコンパイルエラーになります。
'<null>' と 'int'' の間に暗黙的な変換がないため、条件式の型がわかりません。
「なんで分からないんだッ!どう見たってNullable<int>だろ!(バンバンッ」とやりたくなりますが、悔しいことにエラーです。第2オペランドと第3オペランドが戻す型が同じでなければならない、というのは分からんでもないのですが、これはコンパイラ解釈で通して欲しい。「大体、Null許容型と値型の間には暗黙的な型変換が存在する)じゃないか!」ということで、C#チームさん何とかしてくれないかな...。今のところ僕には浮かんでいないですが、もし通せない理由があるなら教えてほしいです。
超余談ですが、なぜintの後ろにSingle Quoteが2つあるのかは不明です。(コピペミスではありませんw)
解決策.1 : 明示的にインスタンスを生成
コンパイルを通すにはどうするか。いくつか方法がありますが、とりあえずは明示的にインスタンスを生成することです。
static void Main() { var obj = new { Id = 1 }; var id = obj == null ? null : new int?(obj.Id); }
しかし、これはめんどくさい。却下。
解決策.2 : dynamicの利用
「こんな変換を要求されるのはコンパイル時に評価されるからだ!実行時評価なら問題ない!」、と天の邪鬼に考えてdynamicを使ってみます。
static void Main() { dynamic obj = new { Id = 1 }; var id = obj == null ? null : obj.Id; }
通ります。もちろん実行できます。でも、コンパイル時のチェックを飛ばしてどーする。インテリセンスも効かない。...却下。
解決策.3 : 明示的な型変換
「待て×2、三項演算子内でのNull許容型と値型の暗黙的な型変換が許容されていないだけだ。明示的な型変換をすれば大丈夫だろ」ということで型変換をしてみます。
static void Main() { var obj = new { Id = 1 }; var id = obj == null ? null : (int?)obj.Id; }
これはコンパイルOKです。型を指定するだけなのでそれなりに短く書けます。きっとこれが一般的な方法だろうと思いますし、僕もこれまでこう書いて来ました。
解決策.4 : 拡張メソッドの利用
「けど、型名が長かったらどーするんだ!イチイチ型を書くはめんどくさい!ってゆーか、null比較をしてnullを返す定型句すらめんどくさい!世の中にはnull合体演算子)というものまであるくらい、null比較なんてめんどくさい!」ということで、変換メソッドを作っちゃえばいいんじゃないか論を唱え、作ってみました。
static class NullableExtensions { public static U? ToNullable<T, U>(this T instance, Func<T, U> selector) where T : class where U : struct { if (instance == null) return null; if (selector == null) throw new ArgumentNullException("selector"); return (U?)selector(instance); } /* //--- 最初なぜか式木使ってた。自分でも何でこんな事したのか分からない。 //--- 「式木要らなくね?」と指摘してくださった@takeshikさんに感謝。 public static U? ToNullable<T, U>(this T instance, Expression<Func<T, U>> expression) where T : class where U : struct { if (instance == null) return null; if (expression == null) throw new ArgumentNullException("expression"); return (U?)expression.Compile().Invoke(instance); } */ }
これを利用することで、次のように三項演算子を隠蔽する形で、型名も必要なく、かつタイプセーフに記述することができます。
static void Main() { //--- プロパティでもデリゲートでもOK var obj = new { Id = 1, GetId = (Func<int>)(() => 2), }; var id1 = obj.ToNullable(x => x.Id); var id2 = obj.ToNullable(x => x.GetId()); //--- nullを渡せばnullが返る obj = null; var id3 = obj.ToNullable(x => x.Id); }
(ドヤァ...
まとめ
作る前は「これでちょっと楽になるやろー」と思っていたのですが、作り終わってみると、隠蔽し過ぎてて「なんだコイツ」状態を拭い去れない...。とはいえ、ASP.NET MVCなどをやっていると、DBのレコードを扱うときにNullable<int>を使うことが多々発生するので、こういうのはあったらあったで便利かもしれません。