データベースの型クラスを自動生成する過程でハマったのでメモ。
現象
- 実行時に
int?
(Nullable<int>
)となっている変数に対して、型情報を取得するとint
が返ってくる
原因
Nullable<T>
型がボックス化されるとき、
-
HasValue
がfalse
ならばnull
に -
HasValue
がtrue
ならばT
型に
自動でボックス化される。
そのため実行時の型を調べようとobject.GetType()
を呼ぶと、T
型が返ってくるか、null
にボックス化されるため、NullReferenceException
の例外が発生する。
対象の型がNullableかどうか検証するには、Nullable.GetUnderlyingType
を使うこと。
検証コード
Try.NETに以下のコードを貼り付けることで、オンラインで検証できます。
Sample.cs
using System;
public class Program
{
public static void Main()
{
// typeof(int?)はNullable<int>を返す
Console.WriteLine($"typeof(int?) = {typeof(int?)}");
int? nullableThree = 3;
int? nullValue = null;
int notNullTen = 10;
// Nullable<T>.GetType()は未定義のため、object.GetType()が呼ばれる。(ボックス化)
// その際HasValue == trueの場合はT型に、falseの場合はnullにボックス化される。(Nullable<int>にはボックス化されない)
// int型にボックス化されるため、intを返す
Console.WriteLine($"nullableThree type is {nullableThree.GetType()}");
try
{
// nullにボックス化されるため、NullReferenceExceptionの例外発生
Console.WriteLine(nullValue.GetType());
}
catch (NullReferenceException)
{
Console.WriteLine("nullValue causes NullReferenceException");
}
// is演算子も同様。n is intとn is int?は同じ結果を返す(Nullable<int>に変えても同様)
Console.WriteLine($"nullableThree is int = {nullableThree is int}");
Console.WriteLine($"nullableThree is int? = {nullableThree is int?}");
Console.WriteLine($"nullValue is int = {nullValue is int}");
Console.WriteLine($"nullValue is int? = {nullValue is int?}");
// int型の変数に対するn is int?に至っては「常にtrueを返すけどいい?」という注釈が出る
Console.WriteLine($"notNullTen is int? = {notNullTen is int?}");
// Nullable<int>とintの区別にはNullable.GetUnderlyingType(System名前空間)を使用する
Console.WriteLine($"IsNullableType(notNullTen) = {IsNullableType(notNullTen)}");
Console.WriteLine($"IsNullableType(nullableThree) = {IsNullableType(nullableThree)}");
// リフレクションでプロパティやフィールドの型を取得した場合はNullable<T>になる
Console.WriteLine(typeof(Foo).GetProperty(nameof(Foo.Value)).PropertyType);
}
public class Foo
{
public int? Value { get; set; }
}
public static bool IsNullableType<T>(T o) => Nullable.GetUnderlyingType(typeof(T)) != null;
}
実行結果
typeof(int?) = System.Nullable`1[System.Int32]
nullableThree type is System.Int32
nullValue causes NullReferenceException
nullableThree is int = True
nullableThree is int? = True
nullValue is int = False
nullValue is int? = False
notNullTen is int? = True
IsNullableType(notNullTen) = False
IsNullableType(nullableThree) = True
System.Nullable`1[System.Int32]