【ゲーム開発のためのC#入門講座・基礎強化編】nullに注意しよう【#7】

2022-01-223.0_C#基礎強化編

ぬるぽって聞いたことありますか?

一昔前のネット用語「ぬるぽ」をご存じですか?

シュタインズ・ゲートで知った、なんて若い方もいるかもしれませんね。

この「ぬるぽ」はJavaという言語で発生するエラー「NullPointerException」が語源のネット用語です。「よく見かけるエラーで、対応が面倒な憎いやつ」であることから、せめて可愛らしく言おうぜという経緯から生まれたとかなんとか。

C#の場合は同じ原因のエラー「NullReferenceException」が、これまた 「よく見かけるエラーで、対応が面倒な憎いやつ」 として今後皆さんの前に度々登場することになります。

名前から「Null (ヌル) を参照(Reference)してエラー(Exception)になった」らしいことは何となく察せますが、この「Null(ヌル)」とはいったい何なのでしょうか?

空っぽの参照型には何が入っている?

参照型は型情報だけではメモリサイズがわからないことから、「new」という命令実行時に初めてヒープメモリにデータが作成されるのでしたね。そしてそのアドレスはスタックメモリに保存されるのでした。

それでは、下記プログラムのint型配列変数「A」には何が入っていると思いますか?

public class Hello{
    public static void Main(){
        int[] A;
    }
}

空っぽの参照型には何が入っている? と聞くとちょっと哲学的にも思えますね。

結論からいえば、何も代入していないのだから何も入っていません。当然ですね。

では、この何も入っていない変数を参照して、何か処理を実行しようとするとどうなるでしょう?

参照型ですから、まずはスタックメモリに格納されているヒープメモリのアドレスを参照しようとしますよね。でもメモリは初期状態のままなので、実際にどこかにある実データの場所を指し示している訳ではありません。

結果、存在しないデータを参照しようとしてエラーが発生します。

このように、実データを参照することができない無効なアドレスが設定されている状態、つまり変数が空っぽの状態のことをnullといいます。

nullのデータを参照しようとすると、 先程ご紹介した「NullReferenceException」 というエラーが発生してしまうので、注意が必要です。

どういう時に注意すればいいの?

このnullで一番注意しなければいけないポイント、それは戻り値です。

例えば下記のようなプログラムがあったとします。

public class Hello{
    public static void Main(){
        string[] names = {
            "He",
            "Ro"
        };
        string name = FindHero(names);
        System.Console.WriteLine(name.Length);
    }
    public static string FindHero(string[] names)
    {
        for (int i = 0; i < names.Length; i++)
        {
            if (names[i] == "Hero")
            {
                return names[i];
            }
        }
        return null;
    }
}

関数「FindHero」は、引数のstring型配列の中に「Hero」という文字列が存在する場合はその文字列を、存在しない場合はnullを返すというものです。

関数というのは、戻り値の型を指定した場合、必ず何らかの値を返さなければならないという決まりがあります。

そのため、上記のような「~~を検索する」系の関数の場合、該当のデータを見つけられなかった時はデータが存在しないことを意味するnullを返すしかないのです。

結果、必ず有効なデータが返されるものだと思い込んでしまうと、戻り値を使った処理を実行しようとした時に「NullReferenceException」に遭遇してしまうという訳です。

実際に実行してみると、下記のようにエラーが出てしまいます。

出力エリア

Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object
at Hello.Main () [0x0001f] in /workspace/Main.cs:8
[ERROR] FATAL UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object
at Hello.Main () [0x0001f] in /workspace/Main.cs:8

nullかどうか事前にチェックしよう

対処法にはいくつか選択肢があるのですが、一番わかりやすいのは事前にnullかどうかチェックするというものです。

これは比較演算子を使って実現することができます。

試しに先程のプログラムを、エラーが発生しないよう変更してみましょう。

public class Hello{
    public static void Main(){
        string[] names = {
            "He",
            "Ro"
        };
        string name = FindHero(names);
        // ↓ここで事前にチェックしてるよ!
        if (name != null)
        {
            System.Console.WriteLine(name.Length);   
        }
        else
        {
            System.Console.WriteLine("nullだったよ!"); 
        }
    }
    public static string FindHero(string[] names)
    {
        for (int i = 0; i < names.Length; i++)
        {
            if (names[i] == "Hero")
            {
                return names[i];
            }
        }
        return null;
    }
}
出力エリア

nullだったよ!

無事、変数がnullであってもエラーを発生させずに処理を終えることができました。

戻り値としてnullが返される可能性があるならば、このように事前にチェックした上で利用するよう気を付けた方がよいでしょう。

Unityでの活躍ポイント

実はUnityで頻繁に発生しうるエラーが「NullReferenceException」です。

なぜならば、Unityはキャラクターやマップに配置したオブジェクトなど、大量の参照型データを扱う機会が非常に多いからです。そしてその検索用に用意された関数では、該当するデータが見当たらなかった場合にnullが返されるようになっています。

押し寄せるエラーの波

結果、こんな風に超膨大な量の「NullReferenceException」が発生することも珍しくありません。

nullとは何なのか、そしてそれに対処するにはどうしたらよいのかを学ぶことで、スムーズに対応ができるようになります。

忘れないようにしておきましょう!

実践演習

それでは実際にnull参照によるエラーを回避してみましょう。

演習①nullじゃない時だけ処理を実行しよう

仕様

下記のプログラムはエラーが発生してしまいます。エラーが発生しないよう、関数「CountLength」にて、引数がnullじゃなかった時だけ加算処理を実行するようにしてください。

テンプレート
public class Hello{
    public static void Main(){
        string text = null;
        System.Console.WriteLine(CountLength(text, "hoge")); 
    }
    public static int CountLength(string text, string text2)
    {
        int count = 0;
        count += text.Length;
        count += text2.Length;
        return count;
    }
}

演習②nullだったら別の値を返すようにしよう

仕様

下記のプログラムはエラーが発生してしまいます。エラーが発生しないよう、関数「ReplaceNull」にて、ひとつ目の引数がnullだった場合、代わりにふたつ目の引数を戻り値として返してください。

テンプレート
public class Hello{
    public static void Main(){
        string text = null;
        text = ReplaceNull(text, "代替テキスト");
        System.Console.WriteLine(text); 
    }
    public static string ReplaceNull(string text, string altText)
    {
        // ↓ここにひとつ目の引数がnullだった場合の処理を実装しよう!
        
        // ↑ここまで
        return text;
    }
}

答え合わせ

演習①の答え

public class Hello{
    public static void Main(){
        string text = null;
        System.Console.WriteLine(CountLength(text, "hoge")); 
    }
    public static int CountLength(string text, string text2)
    {
        int count = 0;
        if (text != null)
        {
            count += text.Length;
        }
        if (text2 != null)
        {
            count += text2.Length;
        }
        return count;
    }
}

演習②の答え

public class Hello{
    public static void Main(){
        string text = null;
        text = ReplaceNull(text, "代替テキスト");
        System.Console.WriteLine(text); 
    }
    public static string ReplaceNull(string text, string altText)
    {
        // ↓ここにひとつ目の引数がnullだった場合の処理を実装しよう!
        if (text == null)
        {
            return altText;
        }
        // ↑ここまで
        return text;
    }
}

まとめ

  • nullとは、実データを参照することができない、変数が空っぽの状態のこと
  • 関数(特に検索系)の戻り値で使われることが多いので、戻り値を使った処理での「NullReferenceException」に注意
  • 事前にnullチェックを行うなど、対応策を忘れないようにしておきましょう

それでは、今回もお疲れ様でした!

また次の記事でお会いしましょう!

お借りした素材一覧

この記事では下記サイト様の素材をお借りしています。

ありがとうございました!

かわいいフリー素材集 いらすとや (irasutoya.com)

Posted by yuumekou