C# NullReferenceException の原因と解決方法

System.NullReferenceException: Object reference not set to an instance of an object. とは

C#開発者にとって「NullReferenceException」は最も頻繁に遭遇するエラーの一つです。このエラーは、null(何も参照していない状態)であるオブジェクトに対して、メンバー(メソッドやプロパティ)にアクセスしようとしたときに発生します。

null値のオブジェクトのメンバーにアクセスしようとすると、この例外が発生します。 オブジェクトが実際にインスタンス化されているかを確認することが重要です。



エラーの発生パターン

このエラーは主に以下のようなケースで発生します。

パターン1: オブジェクトの初期化忘れ

public class User
{
    public string Name { get; set; }
}

public class BadExample
{
    public void DisplayUserName()
    {
        User user = null; // または new User() を忘れた場合
        // user が null のため、Name プロパティにアクセスすると NullReferenceException
        Console.WriteLine(user.Name);
    }
}

user変数はnullで初期化されており、Userクラスのインスタンスが割り当てられていません。nullのオブジェクトに対してNameプロパティにアクセスしようとしたため、NullReferenceExceptionが発生します。

public class User
{
    public string Name { get; set; }
}

public class GoodExample
{
    public void DisplayUserName()
    {
        // オブジェクトを適切に初期化する
        User user = new User { Name = "Alice" };
        Console.WriteLine(user.Name);
    }
}

パターン2: メソッドの戻り値がnull

public class UserService
{
    public User GetUserById(int id)
    {
        // ユーザーが見つからない場合、null を返す
        return null;
    }
}

public class BadExample
{
    public void ProcessUser()
    {
        UserService service = new UserService();
        User user = service.GetUserById(1);
        // user が null の可能性があるため、Name プロパティにアクセスすると NullReferenceException
        Console.WriteLine(user.Name);
    }
}

GetUserByIdメソッドがnullを返した場合、user変数にはnullが格納されます。そのnullに対してNameプロパティにアクセスすると、NullReferenceExceptionが発生します。

public class UserService
{
    public User GetUserById(int id)
    {
        return null;
    }
}

public class GoodExample
{
    public void ProcessUser()
    {
        UserService service = new UserService();
        User user = service.GetUserById(1);
        // null チェックを行ってからメンバーにアクセス
        if (user != null)
        {
            Console.WriteLine(user.Name);
        }
        else
        {
            Console.WriteLine("ユーザーが見つかりません。");
        }
    }
}

パターン3: イベントハンドラがnull

public class BadExample
{
    // イベントに購読者が登録されていない可能性がある
    public event Action MyEvent;

    public void RaiseEvent()
    {
        // MyEvent が null の場合、NullReferenceException
        MyEvent();
    }
}

MyEventイベントに購読者(ハンドラ)が登録されていない場合、MyEventnullになります。nullのデリゲートを呼び出そうとしたため、NullReferenceExceptionが発生します。

public class GoodExample
{
    public event Action MyEvent;

    public void RaiseEvent()
    {
        // C# 6.0 以降の null 条件演算子 (?. ) を使用
        MyEvent?.Invoke();

        // 以前の C# バージョンでは以下の方法
        // var handler = MyEvent;
        // if (handler != null)
        // {
        //     handler();
        // }
    }
}
C# 8.0以降では、Nullable Reference Types (NRT) 機能が導入され、参照型がnullになりうるかどうかの意図をコンパイラに伝えることで、NullReferenceExceptionの発生をコンパイル時に警告できるようになりました。

根本原因の特定方法

NullReferenceExceptionが発生した行で、どのオブジェクトがnullであるかを確認してください。IDEのデバッガでブレークポイントを設定し、ステップ実行しながら関連する変数の値を確認すると原因を特定しやすいです。

public class DebugExample
{
    public User GetUser() { return null; }

    public void DebugMethod()
    {
        User user = GetUser(); // このメソッドが null を返す可能性がある

        // ここで user が null であることを確認
        if (user == null)
        {
            Console.WriteLine("user is null at this point.");
        }

        // この行で NullReferenceException が発生する可能性
        // Console.WriteLine(user.Name);
    }
}

防止策とベストプラクティス

オブジェクトにアクセスする前には、必ずnullチェックを行う習慣をつけましょう。C# 6.0以降のnull条件演算子 (?. ) やnull合体演算子 (?? ) を活用することで、簡潔かつ安全にコードを記述できます。

public class PreventionExample
{
    public User GetUser() { return null; }

    public void SafeAccess()
    {
        User user = GetUser();

        // null 条件演算子と null 合体演算子の組み合わせ
        string userName = user?.Name ?? "Unknown";
        Console.WriteLine(userName);

        // Nullable Reference Types を使用 (C# 8.0+)
        // #nullable enable をファイル先頭に追加
        // User? nullableUser = GetUser();
        // if (nullableUser != null)
        // {
        //     Console.WriteLine(nullableUser.Name);
        // }
    }
}
nullチェックを徹底することが、NullReferenceExceptionを防ぐ最も基本的な予防策です。特に、外部からの入力やAPIの戻り値には注意が必要です。

よくある質問(FAQ)

Q
NullReferenceExceptionとArgumentNullExceptionの違いは何ですか?
A

NullReferenceExceptionはnullオブジェクトのメンバーにアクセスしたときに発生します。ArgumentNullExceptionは、メソッドに渡された引数がnullであるべきではないにもかかわらずnullだった場合に、そのメソッド内で明示的にスローされる例外です。

Q
Nullable Reference Types (NRT) はどのように役立ちますか?
A

NRTは、参照型がnullを許容するかどうかをコードで明示的に示すことで、コンパイル時に潜在的なNullReferenceExceptionを警告します。これにより、実行時エラーを未然に防ぎやすくなります。

Q
null条件演算子 (?. ) とnull合体演算子 (??) はどのように使い分けますか?
A

null条件演算子 (?. ) は、オブジェクトがnullでない場合にのみメンバーアクセスを実行し、結果がnullになりえます。null合体演算子 (?? ) は、左側のオペランドがnullの場合に右側のオペランドの値を返します。?.で安全にアクセスし、その結果がnullだった場合に??でデフォルト値を設定するのが一般的です。

免責事項: 当記事の情報は執筆時点の内容に基づいています。最新情報は各公式サイトをご確認ください。当サイトは情報提供を目的としており、資格取得・技術的対応の結果について一切の責任を負いません。




コメント

タイトルとURLをコピーしました