今日はピックアップRoslynなのかC#小ネタ集なのか微妙なライン。

なんか、C# 7で導入される参照戻り値に関して、参照なのにnullを返せるというネタを思いついてしまったり。 ただのネタのつもりだったんですが、案外考えなきゃ行けない事案かもなぁという話。

経緯

参照戻り値で、「null参照」を返したいっていう要望が出ていたりします。

ref? T みたいな専用の記法が欲しいという要望です。 他の人の反応としては、「参照はnullを返すものじゃない。あきらめろ」的な雰囲気。 僕個人の感想としても、「メリットの割に複雑。実装するのは割に合わない」と思います。

なのでそのままスルーしようとしていたところで、ふと、邪悪なアイディアを思いついてしまいます。 「Unsafeクラス使ってnull返せるよ」とかいう。 以下のようなコードでできます。

    unsafe static ref T NullRef<T>() where T : struct => ref Unsafe.AsRef<T>((void*)0);
    unsafe static bool IsNull<T>(ref T r) where T : struct => Unsafe.AsPointer(ref r) == (void*)0;

どういうことかというと、先月18日の小ネタの最後でちょっと話しましたけど、 .NET ランタイムの内部的には参照とポインターの扱いは全く同じです。で、Unsafeクラスは、それを利用して(半分、悪用レベル)ポインターと参照の相互変換する機能を提供しています。名前通り結構安全性を損なう機能で、色々悪用もできます。その1つが、今回の「null参照」。0をポインターに渡して、それを参照に変換してやれば、nullな参照の完成という。

参照にnullは期待しない

参照とポインターの違いの1つに、無効な参照先(要するにnull)を認めるかどうかがあります。ポインターにはnullポインターがありますが、普通、参照先がない参照なんて想定しません。無効な場所を指さないように、コンパイラーが色々制限を掛けているのが参照です。 C#でも「そのつもり」です。 参照引数も、参照戻り値も、通常は必ず有効な参照先を持ちます。

それを台無しにできる程度に悪用可能なのがunsafeコンテキストなわけです。まあ、そういうことが可能だから、コンパイルオプションで「unsafeコードを認める」(/unsafeオプション)をオンにしないと使えなくしているわけですが… 参照戻り値を使うと、/unsafeオプションなしで危ない事ができる可能性が出てきます。

Unsafeクラスの利用自体では、危ない事をするにはポインターを介する必要があって、使う側にも/unsafeオプションが必要です。危ないことしている自覚が出るという意味では、Unsafeクラスはまだ安全な部類と言えます。

ところが、先ほどのコード、再掲になりますが以下のようなものが入ったライブラリを作るとします。

    unsafe static ref T NullRef<T>() where T : struct => ref Unsafe.AsRef<T>((void*)0);
    unsafe static bool IsNull<T>(ref T r) where T : struct => Unsafe.AsPointer(ref r) == (void*)0;

このコードを実装する際にはポインターが含まれているので/unsafeオプションが必要です。一方、引数や戻り値にはポインターが出てこず、これを使う側には/unsafeオプション不要です。通常の、safeなコンテキストで、危ないものが使えている状態になりました。

null参照を認めるべきか

できてしまうものは仕方がない。であれば、改めて、null参照を認めるべきなのかという問題が出てきます。

まあ、前述の通り、普通、「無効な参照」なんて期待しません。他の言語でも見たことがない。「nullがほしければポインターを使え」という感じ。あまり期待されないていないものが存在するのはそれだけでデメリットです。完全に消せるならそれに越したことはない。

ところが、先ほどの邪悪なアイディアにより、現状でも存在しうることがはっきりしました。まあ、敢えてあんなコードを書かない限りは起きない事なので、別に問題なしとして放置するのもありな範囲でしょう。(参照戻り値自体、利用頻度はそれほど多くならないだろうものです。低頻度なものにコストを掛けても割に合いません。)

逆に、積極的にnull参照を認めるならどうすべきでしょう。今現在、null許容参照型っていう言語機能も提案されているくらいですし、少なくとも、null参照があり得るかあり得ないかの区別は、メソッドシグネチャだけ見て区別が付くべきでしょう。そうなると、冒頭のref? Tという書き方が現実味を帯びてきます。

まとめ

Unsafeクラスがなかなかやりたい放題で、通常あり得なさそうな「null参照」を得られることに気づきました。

まあ、Unsafeとかいう名前からして危なそうなものを使って初めてできることですし、参照戻り値自体利用頻度はそう高くない機能になると思われるので、放っておいても問題なさそうなものです。

とはいえ、もしかすると、「null許容参照」みたいな概念も必要になるかもしれません。