C#「オブジェクト参照が必要です」の原因と解決方法|CS0120・NullReferenceExceptionを初心者向けに解説
はじめに
C#を学び始めたばかりの人がよくつまずくエラーに、「オブジェクト参照が必要です」というものがあります。
特に多いのが、次の2つです。
C#CS0120: 静的でないフィールド、メソッド、またはプロパティ 'xxx' で、オブジェクト参照が必要です
C#NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
どちらも「オブジェクト参照」という言葉が出てくるため、同じエラーだと思ってしまいがちです。しかし、実際には原因も発生するタイミングも解決方法も異なります。
CS0120は主に「インスタンスを作らずに、インスタンスメンバーを使おうとしている」場合に発生するコンパイルエラーです。一方、NullReferenceExceptionは「変数の中身がnullのまま、プロパティやメソッドを呼び出している」場合に発生する実行時エラーです。
この記事では、C#の「オブジェクト参照が必要です」というエラーについて、CS0120とNullReferenceExceptionの違い、原因、修正方法を初心者向けにわかりやすく解説します。
1. C#の「オブジェクト参照が必要です」とは?
C#における「オブジェクト参照が必要です」とは、簡単にいうと「使おうとしているものを操作するための実体が必要です」という意味です。
C#では、クラスを定義しただけでは、そのクラスの中にある通常のフィールド、メソッド、プロパティを自由に使えるわけではありません。多くの場合、newを使ってオブジェクトを作成し、そのオブジェクトを通してメンバーにアクセスする必要があります。
たとえば、次のようなクラスがあるとします。
C#class Player
{
public string Name = "勇者";
public void Attack()
{
Console.WriteLine("攻撃した");
}
}
このPlayerクラスは、あくまで「設計図」です。実際にNameやAttack()を使うには、次のようにインスタンスを作成します。
C#Player player = new Player();
Console.WriteLine(player.Name);
player.Attack();
このように、playerというオブジェクト参照を通して、NameやAttack()にアクセスします。
1-1. 「オブジェクト参照が必要です」が表示される場面
「オブジェクト参照が必要です」というエラーは、主に次のような場面で表示されます。
1つ目は、インスタンスメンバーをクラス名から直接呼び出そうとした場合です。
C#class Sample
{
public void Show()
{
Console.WriteLine("Hello");
}
}
class Program
{
static void Main()
{
Sample.Show(); // エラー
}
}
Show()はstaticではない通常のメソッドなので、Sample.Show()のようにクラス名から直接呼び出すことはできません。
2つ目は、変数がnullのままメソッドやプロパティを呼び出した場合です。
C#Player player = null;
Console.WriteLine(player.Name); // 実行時エラー
この場合、playerは何も参照していないため、Nameにアクセスできません。
1-2. CS0120とNullReferenceExceptionの違い
CS0120とNullReferenceExceptionは、どちらも「オブジェクト参照」に関係しますが、まったく別のエラーです。
CS0120は、プログラムを実行する前のコンパイル時に発生します。コードの書き方として、インスタンスが必要なメンバーをクラス名から呼び出している場合などに表示されます。
一方、NullReferenceExceptionは、プログラムの実行中に発生します。コード自体はコンパイルできても、実行したときに変数の中身がnullだった場合に発生します。
たとえるなら、CS0120は「そもそも呼び出し方が間違っている」エラーです。NullReferenceExceptionは「呼び出し方は合っているが、参照先が存在しない」エラーです。
1-3. 初心者が混同しやすい2つのエラー文
初心者が混同しやすい理由は、どちらのエラーメッセージにも「オブジェクト参照」という言葉が含まれているからです。
CS0120では、次のようなエラーが表示されます。
C#静的でないフィールド、メソッド、またはプロパティにオブジェクト参照が必要です
これは、「staticではないメンバーを使うなら、インスタンスを作ってから呼び出してください」という意味です。
一方、NullReferenceExceptionでは、次のようなエラーが表示されます。
C#オブジェクト参照がオブジェクト インスタンスに設定されていません
これは、「変数がnullで、実際のオブジェクトを参照していません」という意味です。
同じ「オブジェクト参照」という言葉でも、CS0120は「参照を使って呼び出していない」、NullReferenceExceptionは「参照先がnullである」という違いがあります。
1-4. まず確認すべきポイント
「オブジェクト参照が必要です」と表示されたら、まず次の点を確認しましょう。
まず、エラーがコンパイル時に出ているのか、実行中に出ているのかを確認します。Visual Studioで赤い波線が表示され、ビルドに失敗する場合はCS0120の可能性が高いです。プログラム実行中に停止する場合はNullReferenceExceptionの可能性が高いです。
次に、エラーメッセージの文言を確認します。
CS0120と表示されているなら、staticではないメンバーを正しく呼び出せているか確認します。
NullReferenceExceptionと表示されているなら、対象の変数がnullになっていないか確認します。
この2つを切り分けるだけで、原因をかなり絞り込めます。
2. CS0120「静的でないフィールド、メソッド、またはプロパティにオブジェクト参照が必要です」の原因
CS0120は、C#の文法上、インスタンスが必要なメンバーを正しく呼び出していないときに発生します。
エラー文は難しく見えますが、意味はシンプルです。
「staticではないメンバーを使うなら、クラス名ではなくオブジェクトから呼び出してください」
ということです。
2-1. staticではないメンバーをクラス名から呼び出している
もっともよくある原因は、staticではないメソッドやプロパティをクラス名から直接呼び出しているケースです。
C#class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
class Program
{
static void Main()
{
int result = Calculator.Add(1, 2); // CS0120
Console.WriteLine(result);
}
}
Addメソッドはstaticではありません。そのため、Calculator.Add()のようにクラス名から直接呼び出すことはできません。
この場合は、次のようにインスタンスを作成します。
C#Calculator calculator = new Calculator();
int result = calculator.Add(1, 2);
Console.WriteLine(result);
クラス名から呼び出せるのは、基本的にstaticが付いたメンバーです。staticが付いていないメンバーは、インスタンスを通して呼び出す必要があります。
2-2. インスタンス化せずにメソッドやプロパティを使っている
クラスを定義しただけでは、そのクラスのオブジェクトが作られたわけではありません。
次のコードを見てください。
C#class User
{
public string Name { get; set; }
public void SayHello()
{
Console.WriteLine("こんにちは、" + Name);
}
}
class Program
{
static void Main()
{
User.Name = "田中"; // CS0120
User.SayHello(); // CS0120
}
}
Userはクラス名です。NameやSayHello()は、Userクラスから作られたオブジェクトが持つメンバーです。
そのため、次のようにnewでインスタンスを作成する必要があります。
C#User user = new User();
user.Name = "田中";
user.SayHello();
Userという設計図から、userという実体を作り、その実体を通して操作している点が重要です。
2-3. Mainメソッドから通常メソッドを直接呼び出している
初心者によくあるのが、Mainメソッドから同じクラス内の通常メソッドを直接呼び出そうとしてCS0120になるケースです。
C#class Program
{
static void Main()
{
ShowMessage(); // CS0120
}
void ShowMessage()
{
Console.WriteLine("メッセージを表示します");
}
}
Mainメソッドはstaticです。一方、ShowMessage()はstaticではない通常のインスタンスメソッドです。
staticメソッドからインスタンスメソッドを直接呼び出すことはできません。
解決方法は2つあります。
1つは、Programクラスのインスタンスを作る方法です。
C#class Program
{
static void Main()
{
Program program = new Program();
program.ShowMessage();
}
void ShowMessage()
{
Console.WriteLine("メッセージを表示します");
}
}
もう1つは、ShowMessage()にもstaticを付ける方法です。
C#class Program
{
static void Main()
{
ShowMessage();
}
static void ShowMessage()
{
Console.WriteLine("メッセージを表示します");
}
}
どちらを選ぶべきかは、そのメソッドがインスタンスごとの状態を使うかどうかで判断します。
2-4. クラスとインスタンスの違いを理解できていない
CS0120の根本原因は、クラスとインスタンスの違いを理解できていないことにあります。
クラスは設計図です。インスタンスは、その設計図から作られた実体です。
たとえば、Carクラスがあるとします。
C#class Car
{
public string Color { get; set; }
public void Drive()
{
Console.WriteLine(Color + "の車が走ります");
}
}
この時点では、「車の設計図」があるだけです。実際の車はまだ存在していません。
C#Car car = new Car();
car.Color = "赤";
car.Drive();
new Car()によって、はじめて実体であるインスタンスが作られます。
ColorやDrive()は、個々の車が持つ情報や動作です。そのため、Car.ColorやCar.Drive()のようにクラス名から直接使うことはできません。
3. CS0120の解決方法
CS0120を解決する方法は、大きく分けて2つあります。
1つは、newでインスタンスを作成してから呼び出す方法です。もう1つは、メンバーにstaticを付けてクラス名から呼び出せるようにする方法です。
ただし、何でもstaticにすればよいわけではありません。C#では、インスタンスメンバーとstaticメンバーを正しく使い分けることが大切です。
3-1. newでインスタンスを作成してから呼び出す
staticではないメソッドやプロパティを使いたい場合は、基本的にnewでインスタンスを作成します。
修正前のコードです。
C#class Message
{
public void Show()
{
Console.WriteLine("Hello");
}
}
class Program
{
static void Main()
{
Message.Show(); // CS0120
}
}
修正後は次のようになります。
C#class Message
{
public void Show()
{
Console.WriteLine("Hello");
}
}
class Program
{
static void Main()
{
Message message = new Message();
message.Show();
}
}
Message message = new Message();によって、Messageクラスのインスタンスを作成しています。
その後、message.Show();のように、作成したインスタンスを通してメソッドを呼び出します。
3-2. staticを付けてクラスから呼び出せるようにする
インスタンスごとの状態を使わない処理であれば、メソッドにstaticを付ける方法もあります。
たとえば、単純な計算処理などはstaticメソッドに向いています。
C#class MathHelper
{
public static int Add(int a, int b)
{
return a + b;
}
}
class Program
{
static void Main()
{
int result = MathHelper.Add(10, 20);
Console.WriteLine(result);
}
}
この場合、Addメソッドは特定のインスタンスの状態を使っていません。そのため、staticメソッドとして定義しても自然です。
ただし、次のようにインスタンスごとの値を扱う場合は、staticにするべきではありません。
C#class User
{
public string Name { get; set; }
public void ShowName()
{
Console.WriteLine(Name);
}
}
Nameはユーザーごとに異なる値です。このような場合は、インスタンスメンバーとして扱うのが適切です。
3-3. インスタンスメンバーとstaticメンバーを使い分ける
CS0120を根本的に防ぐには、インスタンスメンバーとstaticメンバーの違いを理解することが重要です。
インスタンスメンバーは、オブジェクトごとに異なる状態を持ちます。
C#class Player
{
public string Name;
public void Introduce()
{
Console.WriteLine("私は" + Name + "です");
}
}
この場合、プレイヤーごとにNameが異なるため、インスタンスメンバーとして定義するのが自然です。
C#Player player1 = new Player();
player1.Name = "太郎";
Player player2 = new Player();
player2.Name = "花子";
player1.Introduce();
player2.Introduce();
一方、staticメンバーはクラス全体に属します。インスタンスを作らなくても呼び出せますが、特定のオブジェクトごとの状態を扱う用途には向いていません。
C#class AppInfo
{
public static string Version = "1.0.0";
}
このように、アプリ全体で共通する情報などはstaticに向いています。
3-4. 修正前・修正後のコード例
CS0120が発生するコードを、修正前と修正後で確認してみましょう。
修正前です。
C#using System;
class Person
{
public string Name { get; set; }
public void Greet()
{
Console.WriteLine("こんにちは、" + Name + "です");
}
}
class Program
{
static void Main()
{
Person.Name = "佐藤"; // CS0120
Person.Greet(); // CS0120
}
}
NameもGreet()もstaticではないため、クラス名のPersonから直接呼び出すことはできません。
修正後です。
C#using System;
class Person
{
public string Name { get; set; }
public void Greet()
{
Console.WriteLine("こんにちは、" + Name + "です");
}
}
class Program
{
static void Main()
{
Person person = new Person();
person.Name = "佐藤";
person.Greet();
}
}
Person person = new Person();でインスタンスを作成し、person.Nameやperson.Greet()のようにインスタンス経由で呼び出しています。
これがCS0120の基本的な解決方法です。
4. NullReferenceException「オブジェクト参照がオブジェクト インスタンスに設定されていません」の原因
NullReferenceExceptionは、変数がnullの状態で、その変数のプロパティやメソッドにアクセスしたときに発生する実行時エラーです。
コードの文法としては正しくても、実際に実行したときに参照先が存在しなければエラーになります。
たとえば、次のコードはコンパイルできます。
C#string text = null;
Console.WriteLine(text.Length);
しかし、実行するとNullReferenceExceptionが発生します。
textはnullであり、文字列オブジェクトを参照していません。そのため、Lengthプロパティにアクセスできないのです。
4-1. 変数がnullのままプロパティやメソッドを呼び出している
もっとも基本的な原因は、変数がnullのまま使われていることです。
C#class User
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
User user = null;
Console.WriteLine(user.Name); // NullReferenceException
}
}
userはUser型の変数ですが、実際のインスタンスを参照していません。
次のようにnewでオブジェクトを作成すれば、エラーは解消されます。
C#User user = new User();
user.Name = "田中";
Console.WriteLine(user.Name);
変数の型がクラス型であっても、中身が自動的に作られるわけではありません。参照型の変数には、実際のオブジェクトを代入する必要があります。
4-2. 配列・リスト・クラスの初期化を忘れている
配列やリスト、クラスの初期化忘れも、NullReferenceExceptionのよくある原因です。
たとえば、次のコードではリストを宣言しただけで初期化していません。
C#List<string> names = null;
names.Add("田中"); // NullReferenceException
修正するには、new List<string>()でリストを作成します。
C#List<string> names = new List<string>();
names.Add("田中");
配列でも同じです。
C#string[] items = null;
Console.WriteLine(items.Length); // NullReferenceException
修正後です。
C#string[] items = new string[3];
Console.WriteLine(items.Length);
また、クラスの中にあるフィールドを初期化し忘れるケースもあります。
C#class Team
{
public List<string> Members;
public void AddMember(string name)
{
Members.Add(name); // NullReferenceException
}
}
この場合、Membersが初期化されていないため、Addを呼び出せません。
C#class Team
{
public List<string> Members = new List<string>();
public void AddMember(string name)
{
Members.Add(name);
}
}
または、コンストラクターで初期化する方法もあります。
C#class Team
{
public List<string> Members;
public Team()
{
Members = new List<string>();
}
public void AddMember(string name)
{
Members.Add(name);
}
}
4-3. GetComponentやFindの戻り値がnullになっている
UnityでC#を使っている場合、GetComponentやFindの戻り値がnullになってNullReferenceExceptionが発生することがよくあります。
たとえば、次のようなコードです。
C#Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
rb.AddForce(Vector3.up * 10);
}
このコードでは、対象のGameObjectにRigidbodyコンポーネントが付いていない場合、GetComponent<Rigidbody>()がnullを返します。その状態でrb.AddForce()を呼び出すと、NullReferenceExceptionが発生します。
対策として、戻り値がnullでないか確認します。
C#void Start()
{
rb = GetComponent<Rigidbody>();
if (rb != null)
{
rb.AddForce(Vector3.up * 10);
}
else
{
Debug.LogError("Rigidbodyが見つかりません");
}
}
GameObject.Findでも同様です。
C#GameObject player = GameObject.Find("Player");
player.transform.position = Vector3.zero;
シーン内にPlayerという名前のオブジェクトが存在しない場合、playerはnullになります。
C#GameObject player = GameObject.Find("Player");
if (player != null)
{
player.transform.position = Vector3.zero;
}
else
{
Debug.LogError("Playerが見つかりません");
}
Unityでは、オブジェクト名の間違い、コンポーネントの付け忘れ、Inspectorでの参照設定漏れが原因になることが多いです。
4-4. 外部データや戻り値が想定どおり取得できていない
NullReferenceExceptionは、自分でnullを代入した場合だけでなく、メソッドの戻り値や外部データが想定どおり取得できなかった場合にも発生します。
たとえば、ユーザーを検索するメソッドがあるとします。
C#User FindUser(string name)
{
return null;
}
このメソッドを呼び出して、戻り値をそのまま使うと危険です。
C#User user = FindUser("田中");
Console.WriteLine(user.Name); // NullReferenceException
検索結果が見つからなかった場合、userはnullになります。
安全に書くなら、次のようにnullチェックを行います。
C#User user = FindUser("田中");
if (user != null)
{
Console.WriteLine(user.Name);
}
else
{
Console.WriteLine("ユーザーが見つかりませんでした");
}
データベース、API、ファイル読み込み、JSONの変換結果などを扱う場合も同じです。外部から取得するデータは、必ず期待どおりに存在するとは限りません。
5. NullReferenceExceptionの解決方法
NullReferenceExceptionを解決する基本は、「nullになっている変数を特定し、適切に初期化する」ことです。
そのうえで、必要に応じてnullチェックやnull条件演算子を使い、nullが発生しても安全に処理できるようにします。
5-1. newでオブジェクトを初期化する
変数がnullになっている原因が初期化忘れであれば、newでオブジェクトを作成します。
修正前です。
C#User user;
user.Name = "田中"; // エラー
このコードは、ローカル変数の場合は初期化されていないためコンパイルエラーになりますが、フィールドの場合はnullのままになり、使い方によってはNullReferenceExceptionの原因になります。
正しくは次のようにします。
C#User user = new User();
user.Name = "田中";
リストの場合も同じです。
C#List<int> numbers = new List<int>();
numbers.Add(1);
「変数を宣言すること」と「オブジェクトを作成すること」は別です。
C#User user;
これは変数を用意しただけです。
C#User user = new User();
これは変数に新しいオブジェクトを代入しています。
5-2. nullチェックを追加する
外部データや検索結果など、nullになる可能性がある値を扱う場合は、nullチェックを追加します。
C#if (user != null)
{
Console.WriteLine(user.Name);
}
else
{
Console.WriteLine("userはnullです");
}
メソッドの戻り値がnullになる可能性がある場合は、使う前に確認する習慣を付けましょう。
C#User user = FindUser("田中");
if (user == null)
{
Console.WriteLine("ユーザーが見つかりません");
return;
}
Console.WriteLine(user.Name);
先にnullの場合を処理してreturnすると、その後のコードが読みやすくなります。
5-3. null条件演算子?.とnull合体演算子??を使う
C#では、nullを安全に扱うための演算子が用意されています。
null条件演算子?.を使うと、対象がnullでない場合だけメンバーにアクセスできます。
C#Console.WriteLine(user?.Name);
userがnullの場合、user.Nameにはアクセスせず、結果はnullになります。そのため、NullReferenceExceptionを防げます。
さらに、null合体演算子??を使うと、値がnullだった場合の代替値を指定できます。
C#Console.WriteLine(user?.Name ?? "名前未設定");
このコードでは、userがnullの場合やNameがnullの場合に、"名前未設定"が表示されます。
リストでも使えます。
C#int count = items?.Count ?? 0;
Console.WriteLine(count);
itemsがnullなら、Countにアクセスせず、代わりに0を使います。
ただし、?.や??は根本原因を隠してしまうこともあります。本来nullになってはいけない変数に対しては、なぜnullになっているのかを確認することが大切です。
5-4. デバッグでnullになっている変数を特定する
NullReferenceExceptionを解決するには、どの変数がnullなのかを特定する必要があります。
Visual Studioを使っている場合は、エラーが発生した行で停止したときに、変数にマウスカーソルを合わせると現在の値を確認できます。
たとえば、次のコードでエラーが出たとします。
C#Console.WriteLine(user.Profile.Address.City);
この場合、nullの可能性があるのは次のいずれかです。
C#user
user.Profile
user.Profile.Address
エラー行だけを見て「Cityが悪い」と考えるのではなく、途中のどこかがnullになっている可能性を確認します。
一度分解して書くと、原因を特定しやすくなります。
C#Console.WriteLine(user);
Console.WriteLine(user.Profile);
Console.WriteLine(user.Profile.Address);
Console.WriteLine(user.Profile.Address.City);
または、ブレークポイントを設定して、デバッグ実行しながら変数の中身を確認します。
NullReferenceExceptionでは、「エラーが出た行」と「実際にnullになっている変数」を分けて考えることが重要です。
5-5. 修正前・修正後のコード例
NullReferenceExceptionが発生するコードを、修正前と修正後で確認しましょう。
修正前です。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> names = null;
names.Add("田中"); // NullReferenceException
Console.WriteLine(names.Count);
}
}
namesがnullのままAddを呼び出しているため、実行時エラーになります。
修正後です。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> names = new List<string>();
names.Add("田中");
Console.WriteLine(names.Count);
}
}
new List<string>()でリストを初期化しているため、Addを呼び出せます。
nullチェックを使う場合は、次のようにも書けます。
C#using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> names = GetNames();
if (names == null)
{
Console.WriteLine("名前リストが取得できませんでした");
return;
}
names.Add("田中");
Console.WriteLine(names.Count);
}
static List<string> GetNames()
{
return null;
}
}
このように、nullになる可能性がある戻り値は、使う前にチェックすることが大切です。
6. CS0120とNullReferenceExceptionの見分け方
CS0120とNullReferenceExceptionを見分けるには、発生タイミング、エラーメッセージ、原因、対処方法を確認します。
どちらも「オブジェクト参照」に関係するため混同しやすいですが、ポイントを押さえれば簡単に判断できます。
6-1. コンパイル時エラーか実行時エラーかで判断する
CS0120はコンパイル時エラーです。
つまり、プログラムを実行する前に、ビルドの段階でエラーになります。Visual Studioでは、エラー一覧にCS0120と表示されることが多いです。
C#Sample.Show(); // CS0120
このようなコードは、実行前に「呼び出し方が間違っている」と判断されます。
一方、NullReferenceExceptionは実行時エラーです。
C#User user = null;
Console.WriteLine(user.Name); // 実行中にエラー
このコードはコンパイル自体は通ります。しかし、実行したときにuserがnullであるため、エラーが発生します。
6-2. エラーメッセージの文言で判断する
エラーメッセージを見ることでも判断できます。
CS0120の場合は、次のような文言です。
C#静的でないフィールド、メソッド、またはプロパティにオブジェクト参照が必要です
この場合は、staticではないメンバーをクラス名から呼び出していないか確認します。
NullReferenceExceptionの場合は、次のような文言です。
C#オブジェクト参照がオブジェクト インスタンスに設定されていません
この場合は、変数がnullになっていないか確認します。
「静的でない」という言葉があればCS0120、「インスタンスに設定されていません」という言葉があればNullReferenceExceptionと考えると、初心者でも判断しやすくなります。
6-3. 発生タイミングで判断する
発生タイミングも重要です。
コードを書いている途中やビルドしたときに表示されるなら、CS0120の可能性が高いです。
アプリやゲームを実行して、特定の処理に入った瞬間に停止するなら、NullReferenceExceptionの可能性が高いです。
たとえば、Unityでゲームを再生したあとにConsoleへNullReferenceExceptionが出る場合は、実行時に何かの参照がnullになっています。
一方、Visual Studioでビルドできない場合や、Unityでスクリプトのコンパイルエラーとして出ている場合は、CS0120のような文法上の問題を疑います。
6-4. 対処方法の違いを比較表で確認する
CS0120とNullReferenceExceptionの違いを表で整理すると、次のようになります。
| 項目 | CS0120 | NullReferenceException |
|---|---|---|
| 発生タイミング | コンパイル時 | 実行時 |
| 主な原因 | インスタンスメンバーをクラス名から呼んでいる | 変数がnullのまま使われている |
| よくある文言 | 静的でないメンバーにオブジェクト参照が必要 | オブジェクト参照がインスタンスに設定されていない |
| 確認すること | staticかどうか、newしているか | 変数の中身がnullかどうか |
| 主な解決方法 | インスタンスを作る、またはstaticにする | 初期化する、nullチェックする |
| 発生例 | ClassName.Method() | user.Nameでuserがnull |
CS0120は「呼び出し方の問題」、NullReferenceExceptionは「参照先の中身の問題」と覚えると理解しやすいです。
7. オブジェクト参照エラーを防ぐための基本知識
C#のオブジェクト参照エラーを防ぐには、クラス、オブジェクト、インスタンス、static、nullの考え方を理解することが重要です。
エラーが出るたびに場当たり的に修正するのではなく、基本を押さえることで同じミスを減らせます。
7-1. クラス・オブジェクト・インスタンスの違い
クラスは、オブジェクトを作るための設計図です。
C#class Dog
{
public string Name;
public void Bark()
{
Console.WriteLine(Name + "が吠えました");
}
}
このDogクラスは、犬の情報や動作を定義した設計図です。
インスタンスは、その設計図から作られた実体です。
C#Dog dog = new Dog();
dog.Name = "ポチ";
dog.Bark();
new Dog()によって、実際に使える犬のオブジェクトが作られます。
オブジェクト参照とは、そのオブジェクトを指し示すための変数です。
C#Dog dog
このdogが、作成されたDogオブジェクトを参照しています。
クラス名と変数名を混同しないようにしましょう。
C#Dog.Bark(); // エラー
dog.Bark(); // 正しい
7-2. 参照型と値型の違い
C#には、大きく分けて参照型と値型があります。
値型には、int、double、bool、structなどがあります。値型の変数には、値そのものが入ります。
C#int number = 10;
参照型には、class、string、配列、List<T>などがあります。参照型の変数には、オブジェクトそのものではなく、オブジェクトへの参照が入ります。
C#User user = new User();
参照型の変数は、何も参照していない状態としてnullになることがあります。
C#User user = null;
この状態でuser.Nameのようにアクセスすると、NullReferenceExceptionが発生します。
値型は通常nullになりません。ただし、int?のようなnullable型にすると、値型でもnullを扱えます。
C#int? number = null;
NullReferenceExceptionで問題になるのは、主に参照型の変数です。
7-3. staticの正しい使い方
staticは、インスタンスではなくクラスそのものに属するメンバーを定義するときに使います。
たとえば、次のようなユーティリティメソッドはstaticに向いています。
C#class StringHelper
{
public static bool IsEmpty(string text)
{
return string.IsNullOrEmpty(text);
}
}
このメソッドは、特定のインスタンスの状態を必要としません。そのため、次のようにクラス名から呼び出せます。
C#bool result = StringHelper.IsEmpty("");
一方、オブジェクトごとに異なるデータを扱う場合は、staticにしないほうが自然です。
C#class Employee
{
public string Name;
public int Age;
public void ShowInfo()
{
Console.WriteLine(Name + " / " + Age);
}
}
社員ごとにNameやAgeが異なるため、インスタンスメンバーとして扱います。
何でもstaticにすると、状態の管理が難しくなり、テストや拡張もしづらくなることがあります。
staticを使うべきか迷ったら、「そのメンバーは個々のオブジェクトごとに違う値を持つか」を考えましょう。違う値を持つならインスタンスメンバー、全体で共通ならstaticメンバーが向いています。
7-4. nullを発生させない設計の考え方
NullReferenceExceptionを防ぐには、nullチェックを増やすだけでなく、そもそもnullが発生しにくい設計にすることも大切です。
まず、必要なフィールドはコンストラクターで初期化します。
C#class Order
{
public List<string> Items { get; }
public Order()
{
Items = new List<string>();
}
}
このようにしておけば、Itemsがnullのまま使われる可能性を減らせます。
次に、メソッドがnullを返す設計を避けることも有効です。
C#List<string> GetNames()
{
return new List<string>();
}
データがない場合でもnullではなく空のリストを返すようにすると、呼び出し側でNullReferenceExceptionが起きにくくなります。
C#List<string> names = GetNames();
Console.WriteLine(names.Count);
また、C#のnullable参照型を有効にすると、nullの可能性がある変数をコンパイル時に検出しやすくなります。
C#string? name = null;
string message = name ?? "未設定";
nullを完全になくすことは難しいですが、「nullになる可能性がある値」と「nullになってはいけない値」を区別して設計することが重要です。
8. よくある質問
8-1. 「オブジェクト参照」とは簡単にいうと何ですか?
オブジェクト参照とは、作成されたオブジェクトを指し示すための情報です。
たとえば、次のコードではuserがオブジェクト参照です。
C#User user = new User();
new User()で作られたオブジェクトを、userという変数が参照しています。
そのため、次のようにuserを通してプロパティやメソッドを使えます。
C#user.Name = "田中";
user.SayHello();
一方、次のようにnullが入っている場合、何も参照していません。
C#User user = null;
この状態でuser.Nameにアクセスすると、NullReferenceExceptionが発生します。
8-2. staticを付ければすべて解決できますか?
staticを付ければCS0120が解消される場合はありますが、すべてのケースで正しい解決方法とは限りません。
たとえば、次のように個々のユーザーごとに異なる名前を持つ場合、staticにするのは不自然です。
C#class User
{
public string Name;
}
もしNameをstaticにすると、すべてのユーザーで同じNameを共有することになります。
C#class User
{
public static string Name;
}
この場合、ユーザーごとに別々の名前を持てません。
staticは、インスタンスごとの状態を必要としない処理や、クラス全体で共有したい値に使います。CS0120が出たからといって、何でもstaticにするのは避けましょう。
8-3. newを付けてもエラーが直らない原因は?
newを付けてもエラーが直らない場合、いくつかの原因が考えられます。
まず、別の変数がnullになっている可能性があります。
C#User user = new User();
Console.WriteLine(user.Profile.Name);
この場合、userはnullではありません。しかし、user.ProfileがnullであればNullReferenceExceptionが発生します。
次に、newしたインスタンスとは別の変数を使っている可能性もあります。
C#User user1 = new User();
User user2 = null;
Console.WriteLine(user2.Name); // エラー
user1をnewしていても、実際に使っているuser2がnullならエラーになります。
また、CS0120の場合は、newしたインスタンスではなくクラス名から呼び出したままになっていないか確認しましょう。
C#User user = new User();
User.ShowName(); // エラー
正しくは次のように、インスタンス変数から呼び出します。
C#user.ShowName();
8-4. Unityでよく出るオブジェクト参照エラーの原因は?
Unityでよく出る原因は、Inspectorで参照を設定していないこと、GetComponentで対象のコンポーネントが見つからないこと、GameObject.Findで指定した名前のオブジェクトが見つからないことです。
たとえば、次のようなコードがあります。
C#public GameObject player;
void Start()
{
player.transform.position = Vector3.zero;
}
この場合、Inspectorでplayerにオブジェクトを設定していなければ、playerはnullです。
対策として、Inspectorで参照を設定するか、コード内で取得します。
C#void Start()
{
if (player == null)
{
Debug.LogError("playerが設定されていません");
return;
}
player.transform.position = Vector3.zero;
}
GetComponentを使う場合も、対象のコンポーネントが本当に付いているか確認しましょう。
C#Rigidbody rb = GetComponent<Rigidbody>();
if (rb == null)
{
Debug.LogError("Rigidbodyがありません");
return;
}
rb.AddForce(Vector3.up);
Unityでは、Consoleに表示されるエラーメッセージをダブルクリックすると、エラーが発生したスクリプトの行へ移動できます。
8-5. Visual Studioでエラー箇所を効率よく探す方法は?
Visual Studioでは、エラー一覧、赤い波線、ブレークポイント、デバッグ実行を使うと効率よく原因を探せます。
CS0120の場合は、エラー一覧に表示される該当行を確認します。赤い波線が付いている箇所を見れば、クラス名からインスタンスメンバーを呼び出していないか確認できます。
NullReferenceExceptionの場合は、デバッグ実行を使うのが効果的です。エラーが発生した行で停止したら、その行に含まれる変数を確認します。
C#Console.WriteLine(user.Profile.Address.City);
このような行でエラーが出た場合、user、Profile、Addressのどれかがnullの可能性があります。
一度、次のように分解すると原因を見つけやすくなります。
C#var profile = user.Profile;
var address = profile.Address;
var city = address.City;
また、変数にマウスカーソルを合わせたり、ウォッチウィンドウに追加したりすると、現在の値を確認できます。
エラー行だけでなく、「その変数がどこで代入されるはずだったのか」まで追いかけることが大切です。
まとめ
C#の「オブジェクト参照が必要です」というエラーは、初心者がつまずきやすいポイントですが、CS0120とNullReferenceExceptionを分けて考えると理解しやすくなります。
CS0120は、staticではないフィールド、メソッド、プロパティをクラス名から直接呼び出したときに発生するコンパイル時エラーです。解決するには、newでインスタンスを作成してから呼び出すか、必要に応じてメンバーにstaticを付けます。
NullReferenceExceptionは、変数がnullのままプロパティやメソッドを呼び出したときに発生する実行時エラーです。解決するには、newで初期化する、nullチェックを追加する、?.や??を使う、デバッグでnullになっている変数を特定することが重要です。
CS0120は「呼び出し方が間違っている」、NullReferenceExceptionは「参照先が存在しない」と覚えると、原因を切り分けやすくなります。
C#では、クラス、インスタンス、static、null、参照型の理解がとても重要です。エラー文をそのまま暗記するのではなく、「今使おうとしているものは、クラスに属するのか、インスタンスに属するのか」「変数は本当にオブジェクトを参照しているのか」を確認する習慣を身につけましょう。

