xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Null許容型と三項演算子

イマサラですが、三項演算子 (条件演算子) ってコンパクトに書けていいですよね。利用条件というか制約はありますが、オブジェクト初期化子など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>を使うことが多々発生するので、こういうのはあったらあったで便利かもしれません。

でも、それよりも何よりも、コンパイルエラーにならないのが一番かなーとは思います。C#チームさん、お願いしますm( )m