データベースの型クラスを自動生成する過程でハマったのでメモ。
現象
- 実行時に
int?
(Nullable<int>
)となっている変数に対して、型情報を取得するとint
が返ってくる
原因
Nullable<T>
型がボックス化されるとき、
HasValue
がfalse
ならばnull
にHasValue
がtrue
ならばT
型に
自動でボックス化される。
そのため実行時の型を調べようとobject.GetType()
を呼ぶと、T
型が返ってくるか、null
にボックス化されるため、NullReferenceException
の例外が発生する。
対象の型がNullableかどうか検証するには、Nullable.GetUnderlyingType
を使うこと。
検証コード
Try.NETに以下のコードを貼り付けることで、オンラインで検証できます。
Sample.cs
usingSystem;publicclassProgram{publicstaticvoidMain(){// typeof(int?)はNullable<int>を返すConsole.WriteLine($"typeof(int?) = {typeof(int?)}");int?nullableThree=3;int?nullValue=null;intnotNullTen=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 = {nullableThreeisint}");Console.WriteLine($"nullableThree is int? = {nullableThreeisint?}");Console.WriteLine($"nullValue is int = {nullValueisint}");Console.WriteLine($"nullValue is int? = {nullValueisint?}");// int型の変数に対するn is int?に至っては「常にtrueを返すけどいい?」という注釈が出るConsole.WriteLine($"notNullTen is int? = {notNullTenisint?}");// 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);}publicclassFoo{publicint?Value{get;set;}}publicstaticboolIsNullableType<T>(To)=>Nullable.GetUnderlyingType(typeof(T))!=null;}
実行結果
typeof(int?)=System.Nullable`1[System.Int32]nullableThreetypeisSystem.Int32nullValuecausesNullReferenceExceptionnullableThreeisint=TruenullableThreeisint?=TruenullValueisint=FalsenullValueisint?=FalsenotNullTenisint?=TrueIsNullableType(notNullTen)=FalseIsNullableType(nullableThree)=TrueSystem.Nullable`1[System.Int32]