Nullable reference typesの境界の跨ぎ方
C#は8.0でNullable reference typesが加わりましたが、Unity向けのコードなど、依然としてNullable reference typesを有効にできない環境があります。Nullable reference typesが有効なコンテキストのコードとNullable reference typesが無効なコンテキストなコードの境界でのデータのやりとりには追加の注意が必要になります。
以下、Nullable reference typesが有効な環境を #nullable enable, Nullable reference typesが無効な環境を #nullable disable といいます。
#nullable disable の string != #nullable enable の string?
#nullable enable では以下は型チェックに失敗します。
// #nullable enableなコード
void Hoge(string arg) {}
string? hello = ...;
Hoge(hello);
#nullable enable では、単に string としたら そこにnullは入り得ない ことが期待され、そのように型チェックされるため、 string? な hello を Hoge の引数として与えることはできません。
一方、以下はどうなるでしょうか。
// #nullable enableなコード
void Hoge(string arg) {}
// #nullable disableなコード
string hello = ...;
Hoge(hello);
これは コンパイルが通ってしまいます。 #nullable disable なので hello にはnullが 入り得る のですが、それを #nullable enable な関数 Hoge への string 型の引数として与えることができてしまいます。
C#は、 #nullable disable から #nullable enable へのデータの受け渡し時に、その値が #nullable enable での値域にあることの保証を プログラマに委ねています。 これを守らなかった者に与えられるのは起き得ないはずの例外です。
// #nullable enableなコードのHogeの実装例
void Hoge(string arg)
{
// #nullable enableなので、argがnullであることは考慮しなくていい (するべきではない)
// したがってLengthを取ることは常に安全で例外が飛ぶことがない
if (arg.Length > 5)
{
// ...
}
}
// #nullable disableなコード
string hello = null;
Hoge(hello);
Pitfalls
C#は鉄壁の安全性を誇る言語ではないので、このような境界を跨ぐ場合以外にも非nullableな型の変数にnullが入りうるケースがあります。
// #nullable enableなコード
struct Hello
{
string World;
}
Hello data = default;
このコードは コンパイルが通ります。 そして data.World は null を指します (structのゼロ初期化のため)。この例は 公式ドキュメントのPitfalls に挙げられています。
Discussion