System.NullReferenceExceptionはなぜ起こる?
プログラムを書いていると時々Nullエラーに遭遇します
特にUnityでは、参照忘れのエラーとして頻繁に出現しているでしょう
ここでは、なるべく短いC#のコードでこのエラーを再現し、どのようなことが起こっているのかを見ていきましょう
学習の条件
一度クラスやインスタンスの作成について学んだことがあることを前提とします
参照先がないコード
次のコードはエラーになります
実行しようとしてもエラーになります
なぜでしょうか?
class Program
{
private static void Main(string[] args)
{
Player player;
Console.WriteLine(player.hp);
}
}
class Player
{
public int hp = 10;
}
Visual Studio for Macでの画面
では、次の行を追加すればどうでしょう
player = new Player();
問題なく動作しましたね(10と表示)
では、次のようにしたらどうでしょう
player = null;
Visual Studio for Macでの画面
明らかなエラーにはなりませんし、実行もできます
ただし、次のエラーが出ます
System.Null Reference Exception
Object reference not set to an instance of an object
ローカル表示のウィンドウで、playerの値を確認すると(null)となっていますね
これはわかりやすいエラーコードです
図で表すと次のようですね
実は、newキーワードは、メモリの参照先を割り当てる仕事をしているのです
なぜUnityでは分かりづらい?
Unityでは、管理されたゲームオブジェクトとコンポーネントは、プログラマーが
new [クラス名]
でインスタンスを割り当てることを基本的にしません
エンジン内部でインスタンスを割り当てているからです
イメージ的には次のようなコードを参考にするといいでしょう
Unityイメージコード
class Program
{
private static void Main(string[] args)
{
Player player;
player = Instantiate();
Console.WriteLine(player.hp);
}
static Player Instantiate()
{
return new Player();
}
}
class Player
{
public int hp = 10;
}
C#の時と同じように
return new Player();
のところを
return null;
に置き換えてみると、nullエラーになります
Unityの場合、プログラマに見えているところが、Mainメソッドだけと考えれば、エラーを追及するのが難しいことがわかります
コードの説明
正常コードの場合
Unityイメージコードをベースに、Instantiateメソッドのブロックは、次のようになっている
return new Player();
このC#コードは、Player
というクラスを定義し、それを使用してオブジェクトを生成する方法を示しています。コードの中で、Instantiate()
という静的メソッドが定義され、このメソッドは新しいPlayer
オブジェクトを生成して返します。Main()
メソッドは、Instantiate()
メソッドを呼び出してPlayer
オブジェクトを取得し、そのオブジェクトのhp
プロパティをコンソールに出力します。
Player
クラスは、hp
というpublicな整数型フィールドを持っています。このフィールドは、インスタンス化された各Player
オブジェクトに対して、初期値として10が割り当てられます。
Main()
メソッドの冒頭では、Player
型のplayer
変数が宣言され、初期化されていません。次に、player
変数にInstantiate()
メソッドを呼び出すことで生成されたPlayer
オブジェクトが割り当てられます。最後に、player
オブジェクトのhp
フィールドがコンソールに出力されます。
このコードを実行すると、コンソールに10という値が表示されます。
使っているデザインパターンは?
このコードは、明示的にどのデザインパターンを使っているわけではありません。ただ、基本的なオブジェクト指向プログラミングの原則に基づいています。
Player
クラスは、hp
というフィールドを持ち、これはint
型の値で初期値が設定されています。また、Instantiate()
メソッドは、新しいPlayer
オブジェクトを生成して返すシンプルなファクトリメソッドのように見えます。しかし、これらは明示的なデザインパターンではなく、単にオブジェクト指向プログラミングの基本原則に基づいています。
ただし、Instantiate()
メソッドがファクトリメソッドのパターンに類似しているため、このコードはファクトリメソッドのパターンの一部として考えることができます。また、このコードが示しているように、オブジェクトの生成と初期化を専門に担当するファクトリメソッドは、オブジェクト指向プログラミングでよく使われる慣習です。
エラーコードの場合
正常時との違いは、メソッドの戻り値が次のようになっている点です
return null;
このC#コードは、Player
というクラスを定義し、それを使用してオブジェクトを生成する方法を示しています。コードの中で、Instantiate()
という静的メソッドが定義され、このメソッドはnull
を返します。Main()
メソッドは、Instantiate()
メソッドを呼び出してPlayer
オブジェクトを取得し、そのオブジェクトのhp
プロパティをコンソールに出力します。
Player
クラスは、hp
というpublicな整数型フィールドを持っています。このフィールドは、インスタンス化された各Player
オブジェクトに対して、初期値として10が割り当てられます。
Main()
メソッドの冒頭では、Player
型のplayer
変数が宣言され、初期化されていません。次に、player
変数にInstantiate()
メソッドを呼び出すことで生成されたnull
が割り当てられます。最後に、player
オブジェクトのhp
フィールドがコンソールに出力されます。
このコードを実行すると、NullReferenceException
がスローされ、実行時エラーが発生します。player
オブジェクトがnull
であるため、そのフィールドであるhp
にアクセスすることができず、例外がスローされます。
このコードは、実行時エラーを発生させるためのデモンストレーションとして機能しています。Instantiate()
メソッドがnull
を返すように定義されているため、実際にはPlayer
オブジェクトが生成されていないため、player
変数にはnull
が割り当てられます。しかし、Main()
メソッドではplayer
変数を使用して、Player
オブジェクトのフィールドにアクセスしようとしているため、実行時エラーが発生します。
Unityにさらに近づける
class Program
{
// Startメソッドのシイミュレート
private static void Main(string[] args)
{
Player player;
// ゲームオブジェクトの取得のシミュレート
player = GameObject.Find();
// 取得したオブジェクトのプロパティ(インスペクターの値)を確認
Console.WriteLine(player.hp);
}
}
// GameObject型のシミュレート(ヒエラルキーに表示されるもの)
public class GameObject
{
// コンストラクタ(playerフィールドの初期化
static GameObject()
{
// このコード行をコメントアウトするとNullエラーになります
player = new Player();
}
// ゲームオブジェクト取得のシミュレート(本来はゲームオブジェクト型を取得)
static Player player;
// Findメソッドでゲームオブジェクト取得のシミュレート
public static Player Find()
{
return player;
}
}
public class Player
{
public int hp = 10;
}
ディスカッション
コメント一覧
まだ、コメントがありません