例外のトラブルシューティング : System.NullReferenceException
値が null である 参照型 (C#、Visual Basic) のメソッドまたはプロパティの使用を試みると、NullReferenceException が発生します。 たとえば、初めに new キーワード (Visual Basic では New) を使用せずにオブジェクトの使用を試みたり、値が null (Visual Basic では Nothing) に設定されているオブジェクトの使用を試みたりした可能性があります。
この記事のセクション
この記事で使用されるクラス
NullReferenceExceptions の共通した原因
開発中の NullReferenceExceptions の発生元の確認
NullReferenceExceptions の回避
リリース コード内の NullReferenceExceptions の処理
この記事で使用されるクラス
この記事中の例のほとんどは次のクラスの一方または両方を使用します。
public class Automobile
{
public EngineInfo Engine {get; set;}
}
public class EngineInfo
{
public EngineInfo() { }
public EngineInfo(string powerSrc, double engineSize)
{
Power = powerSrc;
Size = engineSize;
}
public double Size { get; set; }
public string Power = null;
}
Public Class Automobile
Public Property Engine As EngineInfo
End Class
Public Class EngineInfo
Public Sub New()
End Sub
Public Sub New(powerSrc As String, engineSize As Double)
Power = powerSrc
Size = engineSize
End Sub
Public Property Size() As Double
Public Power As String = Nothing
End Class
この記事のその他のセクション
NullReferenceExceptions の共通した原因
任意の参照型変数は null にすることができます。 ローカル変数、クラスのプロパティ、メソッド パラメーター、およびメソッドの戻り値はすべて null 参照を含むことができます。 これらの変数が null の場合にメソッドまたはプロパティを呼び出すと NullReferenceException が発生します。 具体的な例を次に示します。
ローカル変数またはメンバー フィールドは宣言されているが初期化されていない
プロパティまたはフィールドが null である
メソッド パラメーターが null である
メソッドの戻り値が null である
コレクションまたは配列の中のオブジェクトが null である
条件のために作成されないオブジェクト
メソッドに参照により渡されるオブジェクトが null に設定される
ローカル変数またはメンバー フィールドは宣言されているが初期化されていない
この単純なエラーは Visual Basic コード内で最も頻繁に発生します。 out パラメーターとして渡される変数を宣言する場合などを除き、C# コンパイラではローカル参照変数を初期化せずに使用できません。 Visual Basic コンパイラは警告を生成します。
次の C# コードの強調表示された行は、このコンパイラ エラーを生成します。
未割り当てのローカル変数 engine の使用
次の Visual Basic コードの強調表示された行は、コンパイラ警告 BC42104 を生成します。
変数 engine は、値が割り当てられる前に参照によって使用されています。 実行時には、null 参照の例外が発生する可能性があります。
また、その業は実行時に NullReferenceException をスローします。
public void NullReferencFromUninitializedLocalVariable()
{
EngineInfo engine;
Console.WriteLine(engine.ToString());
}
Public Sub NullReferencFromUninitializedLocalVariable()
Dim engine As EngineInfo
Console.WriteLine(engine.ToString())
End Sub
このセクションの内容
この記事のセクション
プロパティまたはフィールドが null である
クラスが作成されると、クラスのフィールドとプロパティは自動的に既定値に初期化されます。 参照型の既定値は null (Visual Basic の場合は Nothing) です。フィールドまたはプロパティの値が null の場合に親クラスのフィールドまたはプロパティでメンバー メソッドを呼び出すと NullReferenceException が発生します。
この例で、car の Engine プロパティは null に自動初期化されるため、強調表示された行は NullReferenceException をスローします。
public void NullReferenceFromProperty()
{
var car = new Automobile();
Console.WriteLine(car.Engine.ToString());
}
Public Sub NullReferenceFromProperty()
Dim car = New Automobile()
Console.WriteLine(car.Engine.ToString())
End Sub
このセクションの内容
この記事のセクション
メソッド パラメーターが null である
参照型であるメソッド パラメーターは null (Visual Basic の場合は Nothing) になることができます。 null であるパラメーター値でメンバー メソッドまたはプロパティを呼び出すと NullReferenceException が発生します。
この例で、BadEngineInfoPassedToMethod は null であるパラメーターを使用して NullReferenceFromMethodParameter を呼び出すため、強調表示された行は NullReferenceException をスローします。
public void BadEngineInfoPassedToMethod()
{
EngineInfo eng = null;
NullReferenceFromMethodParameter(eng);
}
public void NullReferenceFromMethodParameter(EngineInfo engine)
{
Console.WriteLine(engine.ToString());
}
Public Sub BadParameterPassedToMethod() As EngineInfo
Dim eng As EngineInfo = Nothing
NullReferenceFromMethodParameter(eng)
End Sub
Public Sub NullReferenceFromMethodParameter(engine As EngineInfo)
Console.WriteLine(engine.ToString())
End Sub
このセクションの内容
この記事のセクション
メソッドの戻り値が null である
参照型を返すメソッドは null (Visual Basic の場合は Nothing) を返すことがあります。 参照が null の場合、返される参照型のメソッドまたはプロパティを呼び出すと、NullReferenceException が発生します。
この例で、BadGetEngineInfo の呼び出しは NullReferenceFromMethodParameter メソッドで null 参照を返すので、強調表示された行は NullReferenceException をスローします。
public EngineInfo BadGetEngineInfo()
{
EngineInfo engine = null;
return engine;
}
public void NullReferenceFromMethodReturnValue()
{
var engine = BadGetEngineInfo();
Console.WriteLine(engine.ToString());
}
Public Function BadGetEngineInfo() As EngineInfo
Dim engine As EngineInfo = Nothing
Return engine
End Function
Public Sub NullReferenceFromMethodReturnValue()
Dim engine = BadGetEngineInfo()
Console.WriteLine(engine.ToString())
End Sub
このセクションの内容
この記事のセクション
コレクションまたは配列の中のオブジェクトが null である
参照型のリストまたは配列には null である項目が含まれていることがあります。 null であるリスト項目のメソッドまたはプロパティを呼び出すと NullReferenceException が発生します。
この例で、BadGetCarList の呼び出しは null である項目を返すため、NullReferenceFromListItem() 内の強調表示された行は NullReferenceException をスローします。
public Automobile[] BadGetCarList()
{
var autos = new Automobile[10];
for (int i = 0; i autos.Length; i++)
{
if (i != 6)
{
autos[i] = new Automobile();
}
}
return autos;
}
public void NullReferenceFromListItem()
{
var cars = BadGetCarList();
foreach (Automobile car in cars)
{
Console.WriteLine(car.ToString());
}
}
Public Function BadGetCarList() As Automobile()
Dim autos = New Automobile(10) {}
For i As Integer = 0 To 9
If i <> 6 Then
autos(i) = New Automobile()
End If
Next
Return autos
End Function
Public Sub NullReferenceFromListItem()
Dim cars = BadGetCarList()
For Each car As Automobile In cars
Console.WriteLine(car.ToString())
Next
End Sub
このセクションの内容
この記事のセクション
条件のために作成されないオブジェクト
参照型が条件ブロック内で初期化される場合、条件が false のときはオブジェクトが作成されません。
この例で、NullReferenceFromConditionalCreation の強調表示された行は、DetermineTheCondition() メソッドが true を返す場合にのみ engine 変数を初期化するので、NullReferenceException をスローします。
public bool DetermineTheCondition()
{
return false;
}
public void NullReferenceFromConditionalCreation()
{
EngineInfo engine = null;
var condition = DetermineTheCondition();
if (condition)
{
engine = new EngineInfo();
engine.Power = "Diesel";
engine.Size = 2.4;
}
Console.WriteLine(engine.Size);
}
Public Function DetermineTheCondition() As Boolean
Return False
End Function
Public Sub NullReferenceFromConditionalCreation()
Dim engine As EngineInfo = Nothing
Dim condition = DetermineTheCondition()
If condition Then
engine = New EngineInfo()
engine.Power = "Diesel"
engine.Size = 2.4
End If
Console.WriteLine(engine.Size)
End Sub
このセクションの内容
この記事のセクション
メソッドに渡されるオブジェクトのプロパティが null に設定される
オブジェクトをパラメーターとして値によりメソッドに渡す場合 (C# の場合は ref または out キーワードを使用せず、Visual Basic の場合は ByRef キーワードを使用しない)、メソッドはパラメーターのメモリ位置 (パラメーターが指し示す場所) を変更することはできませんが、オブジェクトのプロパティは変更できます。
この例では、NullPropertyReferenceFromPassToMethod メソッドが Automobile オブジェクトを作成し、Engine プロパティを初期化します。 次に、BadSwapCarEngine を呼び出し、新しいオブジェクトをパラメーターとして渡します。 BadSwapCarEngine は Engine プロパティを null に設定するため、NullPropertyReferenceFromPassToMethod の強調表示された行は NullReferenceException をスローします。
public void BadSwapCarEngine(Automobile car)
{
car.Engine = null;
}
public void (Automobile car)
{
car.Engine = new EngineInfo("GAS", 1.5);
BadSwapCarEngine(car);
Console.WriteLine(car.Engine.ToString());
}
Public Sub BadSwapCarEngine(car As Automobile)
car.Engine = Nothing
End Sub
Public Sub NullPropertyReferenceFromPassToMethod()
Dim car As New Automobile()
car.Engine = New EngineInfo("GAS", 1.5)
BadSwapCarEngine(car)
Console.WriteLine(car.Engine.ToString())
End Sub
このセクションの内容
この記事のセクション
メソッドに参照により渡されるオブジェクトが null に設定される
参照型をパラメーターとして参照によりメソッドに渡す場合 (C# の場合は ref または out キーワードを使用し、Visual Basic の場合は ByRef キーワードを使用)、パラメーターが指し示すメモリ位置を変更できます。
参照型を参照によりメソッドに渡す場合、メソッドは参照される型を null (Visual Basic の場合は Nothing) に設定することがあります。
この例で、BadEngineSwapByRef メソッドの呼び出しは stockEngine 変数を null に設定するため、NullReferenceFromPassToMethodByRef の強調表示された行は NullReferenceException をスローします。
public void BadEngineSwapByRef(ref EngineInfo engine)
{
engine = null;
}
public void NullReferenceFromPassToMethodByRef()
{
var stockEngine = new EngineInfo();
stockEngine.Power = "Gas";
stockEngine.Size = 7.0;
BadSwapEngineByRef(ref stockEngine);
Console.WriteLine(stockEngine.ToString());
}
Public Sub BadSwapEngineByRef(ByRef engine As EngineInfo)
engine = Nothing
End Sub
Public Sub NullReferenceFromPassToMethodByRef()
Dim formatStr = "The stock engine has been replaced by a {0} liter {} engine"
Dim stockEngine = New EngineInfo()
stockEngine.Power = "Gas"
stockEngine.Size = 7.0
BadSwapEngineByRef(stockEngine)
Console.WriteLine(stockEngine.ToString())
End Sub
このセクションの内容
この記事のセクション
開発中に null 参照例外のソースを発見
データヒント、ローカル ウィンドウ、およびウォッチ ウィンドウを使用して変数値を確認する
呼び出し履歴を調べて参照変数が初期化されない場所または null に設定される場所を探します。
条件付きブレークポイントを設定して、オブジェクトが null (Visual Basic の場合は Nothing) の場合にデバッグを停止する
データヒント、ローカル ウィンドウ、およびウォッチ ウィンドウを使用して変数値を確認する
変数名の上にマウス ポインターを置き、データヒントで変数の値を確認します。 変数がオブジェクトまたはコレクションを参照する場合、データ型を拡張してプロパティまたは要素を確認できます。
[ローカル] ウィンドウを開き、現在のコンテキストでアクティブな変数を確認します。
ウォッチ ウィンドウを使用して、コードをステップ実行するのにつれて変数がどのように変化するかを確認します。
このセクションの内容
この記事のセクション
呼び出し履歴を調べて参照変数が初期化されない場所または null に設定される場所を探します。
Visual Studio の [呼び出し履歴] ウィンドウには、例外またはブレークポイントでデバッガーが停止するときに完了していないメソッドの名前が一覧表示されます。 [呼び出し履歴] ウィンドウで名前を選択し、[フレームに切り替え] を選択して、実行コンテキストをメソッドに変更し、その変数を確認できます。
このセクションの内容
この記事のセクション
条件付きブレークポイントを設定して、オブジェクトが null (Visual Basic の場合は Nothing) の場合にデバッグを停止する
条件付きブレークポイントを設定して、変数が null の場合に中断することができます。 条件付きブレークポイントは、コレクション内の項目が断続的にしか null にならない場合など、null 参照が頻繁には発生しない場合に役立つことがあります。 条件付きブレークポイントのもう 1 つの利点は、特定の処理ルーチンをコミットする前に問題をデバッグできるということです。
このセクションの内容
この記事のセクション
NullReferenceExceptions の回避
Debug.Assert を使用してインバリアントを確認する
参照型を完全に初期化する
Debug.Assert を使用してインバリアントを確認する
インバリアントは true であることが確かな条件です。 Debug.Assert (System.Diagnostics) ステートメントは、アプリのデバッグ ビルドからのみ呼び出され、リリース コードからは呼び出されません。 インバリアント条件が true でない場合、デバッガーは Assert ステートメントで中断し、ダイアログ ボックスを表示します。 Debug.Assert は、アプリの開発中に条件が変更されていないことをチェックします。 アサーションは、コードを読む他の開発者用に、条件が常に true でなければならないことを記録する役割も果たします。
たとえば、MakeEngineFaster メソッドは、ただ 1 つの呼び出しメソッド (TheOnlyCallerOfMakeEngineFaster) が EngineInfo を完全に初期化するとわかっているため、engine パラメーターが常に null でないと仮定します。 MakeEngineFaster 内のアサートはこの仮定を記録し、仮定が true であることをチェックします。
開発者がパラメーターを初期化しない新しい呼び出しメソッド (BadNewCallerOfMakeEngineFaster) を追加すると、アサートがトリガーされます。
private void TheOnlyCallerOfMakeEngineFaster()
{
var engine = new EngineInfo();
engine.Power = "GAS";
engine.Size = 1.5;
MakeEngineFaster(engine);
}
private void MakeEngineFaster(EngineInfo engine)
{
System.Diagnostics.Debug.Assert(engine != null, "Assert: engine != null");
engine.Size *= 2;
Console.WriteLine("The engine is twice as fast");
}
private void BadNewCallerOfMakeEngineFaster()
{
EngineInfo engine = null;
MakeEngineFaster(engine);
}
Public Sub TheOnlyCallerOfMakeEngineFaster()
Dim engine As New EngineInfo
engine.Power = "GAS"
engine.Size = 1.5
MakeEngineFaster(engine)
End Sub
Private Sub MakeEngineFaster(engine As EngineInfo)
System.Diagnostics.Debug.Assert(engine IsNot Nothing, "Assert: engine IsNot Nothing")
engine.Size = engine.Size * 2
Console.WriteLine("The engine is twice as fast")
End Sub
Public Sub BadNewCallerOfMakeEngineFaster()
Dim engine As EngineInfo = Nothing
MakeEngineFaster(engine)
End Sub
このセクションの内容
この記事のセクション
参照型を完全に初期化する
多くの NullReferenceExceptions を回避するには、できるだけ作成時の状態に近く、参照型を完全に初期化します。
独自のクラスに完全な初期化を追加する
NullReferenceException をスローするクラスを制御する場合、型のコンストラクター内でオブジェクトを完全に初期化することを検討します。 たとえば、完全な初期化を保証するクラスの改訂された例を次に示します。
public class Automobile
{
public EngineInfo Engine { get; set; }
public Automobile()
{
this.Engine = new EngineInfo();
}
public Automobile(string powerSrc, double engineSize)
{
this.Engine = new EngineInfo(powerSrc, engineSize);
}
}
public class EngineInfo
{
public double Size {get; set;}
public string Power {get; set;}
public EngineInfo()
{
// the base engine
this.Power = "GAS";
this.Size = 1.5;
}
public EngineInfo(string powerSrc, double engineSize)
{
this.Power = powerSrc;
this.Size = engineSize;
}
}
Public Class Automobile
Public Property Engine As EngineInfo
Public Sub New()
Me.Engine = New EngineInfo()
End Sub
Public Sub New(powerSrc As String, engineSize As Double)
Me.Engine = New EngineInfo(powerSrc, engineSize)
End Sub
End Class
Public Class BaseEngineInfo
Public Sub New()
' the base engine
Me.Power = "GAS"
Me.Size = 1.5
End Sub
Public Sub New(powerSrc As String, engineSize As Double)
Power = powerSrc
Size = engineSize
End Sub
Public Property Size() As Double
Public Power As String = String.Empty
End Class
注意
大規模またはあまり使用されないプロパティに関して限定的な初期化を使用する
クラスのメモリ使用量を削減し、パフォーマンスを向上させるには、参照型プロパティの限定的な初期化の使用を検討します。 「限定的な初期化」を参照してください。
リリース コード内の NullReferenceExceptions の処理
参照型を使用する前に null (Visual Basic の場合は Nothing) をチェックする
try – catch – finally (Visual Basic の場合は Try – Catch – Finally) を使用して例外を処理する
NullReferenceException は発生後に処理するより、回避することの方が適切です。 例外の処理によって、コードは管理や理解が難しくなることがあるうえ、他のバグが生じることも考えられます。 NullReferenceException は多くの場合、回復不能なエラーです。 こうした場合、例外によってアプリを停止させることが最良の代替策である可能性があります。
ただし、次のように、エラーの処理が有用である状況は多く存在します。
アプリは null であるオブジェクトを無視できます。 たとえば、アプリがデータベース内のレコードを取得して処理する場合、結果として null オブジェクトである、いくつかの不良レコードを無視できる可能性があります。 必要な処理は、ログ ファイルまたはアプリケーションの UI に不良データを記録することだけであることが考えられます。
例外から回復することができます。 たとえば、参照型を返す Web サービスの呼び出しは、接続が失われた場合や接続がタイムアウトした場合には、null を返す可能性があります。 接続を再確立し、呼び出しを再度試すことができます。
アプリの状態を有効な状態に復元できます。 たとえば、NullReferenceException をスローするメソッドを呼び出す前に、情報をデータ ストアに保存する必要がある、複数手順のタスクを実行していることがあります。 初期化されていないオブジェクトがデータ レコードを破損する可能性がある場合、アプリを終了する前に過去のデータを削除できます。
例外の報告が推奨されます。 たとえば、エラーがアプリのユーザーによる間違いに起因する場合、メッセージを生成して、ユーザーが正しい情報を入力するのを支援できます。 問題の解決に役立てるために、エラーに関する情報をログに記録することもできます。 ASP.NET などのいくつかのフレームワークには、アプリのクラッシュを防止するために、すべてのエラーをキャプチャする上位レベルの例外ハンドラーが用意されています。このハンドラーを使用した場合、例外をログに記録することが、その例外が発生したことを知る唯一の方法になる可能性があります。
リリース コードにおける NullReferenceException の処理方法は 2 つあります。
参照型を使用する前に null (Visual Basic の場合は Nothing) をチェックする
オブジェクトを使用する前に null のための明示的なテストを使用すると、try-catch-finally 構造のパフォーマンス低下を回避できます。 ただし、初期化されていないオブジェクトに対する処理を判別して実装する必要はあります。
この例では、CheckForNullReferenceFromMethodReturnValue が BadGetEngineInfo メソッドの戻り値をテストします。 オブジェクトが null でない場合は使用されます。null の場合、メソッドはエラーを報告します。
public EngineInfo BadGetEngineInfo()
{
EngineInfo engine = null;
return engine;
}
public void CheckForNullReferenceFromMethodReturnValue()
{
var engine = BadGetEngineInfo();
if(engine != null)
{
// modify the info
engine.Power = "DIESEL";
engine.Size = 2.4;
}
else
{
// report the error
Console.WriteLine("BadGetEngine returned null")
}
}
public EngineInfo BadGetEngineInfo()
{
EngineInfo engine = null;
return engine;
}
Public Sub CheckForNullReferenceFromMethodReturnValue()
Dim engine = BadGetEngineInfo()
If (engine IsNot Nothing) Then
' modify the info
engine.Power = "DIESEL"
engine.Size = 2.4
Else
' report the error
Console.WriteLine("BadGetEngineInfo returned Nothing")
End If
End Sub
このセクションの内容
この記事のセクション
try – catch – finally (Visual Basic の場合は Try – Catch – Finally) を使用して例外を処理する
組み込み例外処理構造 (C# の場合は try, catch, finally、Visual Basic の場合は Try, Catch, Finally) の使用は、オブジェクトが null でないことのチェックに比べて、NullReferenceExceptions の処理に関してより多くの選択肢を提供します。
この例で、CatchNullReferenceFromMethodCall は 2 つのアサートを使用して、パラメーターがエンジンを含め、完全な自動車を含む仮定を確認します。 try ブロックで、RarelyBadEngineSwap の呼び出しは自動車の Engine プロパティを壊すことがあるので、強調表示された行は NullReferenceException をスローします。 catch ブロックは例外をキャプチャし、例外情報をファイルに書き出し、エラーをユーザーに報告します。 finally ブロックで、メソッドは自動車の状態がメソッドの開始時ほど悪くないことを保証します。
public void RarelyBadSwapCarEngine(Automobile car)
{
if ((new Random()).Next() == 42)
{
car.Engine = null;
}
else
{
car.Engine = new EngineInfo("DIESEL", 2.4);
}
}
public void CatchNullReferenceFromMethodCall(Automobile car)
{
System.Diagnostics.Debug.Assert(car != null, "Assert: car != null");
System.Diagnostics.Debug.Assert(car.Engine != null, "Assert: car.Engine != null");
// save current engine properties in case they're needed
var enginePowerBefore = car.Engine.Power;
var engineSizeBefore = car.Engine.Size;
try
{
RarelyBadSwapCarEngine(car);
var msg = "Swap succeeded. New engine power source: {0} size {1}";
Console.WriteLine(msg, car.Engine.Power, car.Engine.Size);
}
catch(NullReferenceException nullRefEx)
{
// write exception info to log file
LogException(nullRefEx);
// notify the user
Console.WriteLine("Engine swap failed. Please call your customer rep.");
}
finally
{
if(car.Engine == null)
{
car.Engine = new EngineInfo(enginePowerBefore, engineSizeBefore);
}
}
}
Public Sub RarelyBadSwapCarEngine(car As Automobile)
If (New Random()).Next = 42 Then
car.Engine = Nothing
Else
car.Engine = New EngineInfo("DIESEL", 2.4)
End If
End Sub
Public Sub CatchNullReferenceFromMethodCall(car As Automobile)
System.Diagnostics.Debug.Assert(car IsNot Nothing)
System.Diagnostics.Debug.Assert(car.Engine IsNot Nothing)
' save current engine properties in case they're needed
Dim powerBefore = car.Engine.Power
Dim sizeBefore = car.Engine.Size
Try
RarelyBadSwapCarEngine(car)
Dim msg = "Swap succeeded. New engine power source: {0} size {1}"
Console.WriteLine(msg, car.Engine.Power, car.Engine.Size)
Catch nullRefEx As NullReferenceException
' write exception info to log file
LogException(nullRefEx)
' notify user
Console.WriteLine("Engine swap failed. Please call your customer rep.")
Finally
If car.Engine Is Nothing Then car.Engine = New EngineInfo(powerBefore, sizeBefore)
End Try
End Sub
このセクションの内容
この記事のセクション
関連記事
例外のデザイン ガイドライン (.NET Framework デザイン ガイドライン)
例外の処理とスロー (.NET Framework アプリケーションの基本)
方法: 初回例外通知を受信する (.NET Framework 開発ガイド)
方法: PLINQ クエリで例外を処理する (.NET Framework 開発ガイド)
マネージ スレッドにおける例外 (.NET Framework 開発ガイド)
Try...Catch...Finally ステートメント (Visual Basic)
チュートリアル: 同時実行例外の処理 (Visual Studio におけるデータのアクセス)
方法: データバインド (Windows フォーム) により発生するエラーと例外を処理する
ネットワーク アプリにおける例外の処理 (XAML) (Windows)
この記事のセクション