ADO.NET の DataReader でフィールドの値を取得するとき、null 値かどうかを考慮する必要があります。
// nullの場合は例外が発生する
int value = reader.GetInt32(0);
// IsDBNullメソッドで判断してから値を取得する
if (!reader.IsDBNull(0))
{
int value = reader.GetInt32(0);
}
.NET Framework の初期バージョンでは nullable 構造体はありませんでしたので、DataReader には nullable 構造体を用いたメソッドはありません。その後のバージョンアップでもそのような機能は追加されていません。そのため、私は次のような拡張メソッドを定義しています。
public static int? GetInt32OrNull(this IDataRecord record, int index)
{
if (record.IsDBNull(index)) { return null; }
return record.GetInt32(index);
}
public static int GetInt32OrDefault(this IDataRecord record, int index)
{
if (record.IsDBNull(index)) { return 0; }
return record.GetInt32(index);
}
これらの拡張メソッドを用いると、フィールドの値の取得を簡潔かつタイプセーフに実装できます。
// nullable構造体で受け取る int? value = reader.GetInt32OrNull(0); // nullの場合には0が格納される int value = reader.GetInt32OrDefault(0);
拡張メソッドでなくユーティリティメソッドにする場合は次のようにします。
public static class DataRecordUtility
{
public static int? GetInt32OrNull(IDataRecord record, int index)
{
if (record.IsDBNull(index)) { return null; }
return record.GetInt32(index);
}
public static int GetInt32OrDefault(IDataRecord record, int index)
{
if (record.IsDBNull(index)) { return 0; }
return record.GetInt32(index);
}
}
// nullable構造体で受け取る
int? value = DataRecordUtility.GetInt32OrNull(reader, 0);
// nullの場合には0が格納される
int value = DataRecordUtility.GetInt32OrDefault(reader, 0);
なぜ今更このような記事を書いたかというと、今でも nullable 構造体が活用されていないソースコードを見かけることが少なくないためです。 .NET Framework 1 時代やそれよりも前(VisualBasic)のソースコードを流用した開発が繰り返され、その間に積極的な改善や工夫が行われないまま今に至っていることが原因にあると考えています。 そのようなソースコードでは null の可能性がある数値や日付を object 型や string 型で表しており、それによって型変換エラーやパフォーマンスの劣化を引き起こしていたりします。
List<T> などのジェネリックコレクションではなく ArrayList が使われ続けていたりするのも同じような原因だと思います。この記事を見て思い当たる節があるプログラマーの方は少し視野を拡げてみてください。その分のリターンは十分に得られると思います。