public int Life => Life; は、なぜだめなのか?アプリが落ちるのだが!!

2022年11月24日

このコードを見ていかがでしょうか?
VisualStudioでこのコードを記述してもエラーにはなりません
実行するとアプリケーションを落ちます!!
厄介ですよね

なぜそうなるのかの理由を考えていきましょう

コードの解釈

まず、分析するのに次の知識があることが前提になります

  • C#の基礎文法
  • プロパティ

分解してみる

マイクロソフトのプロパティの説明で次のことが書かれています

プロパティ アクセサーは、通常、式の結果を割り当てるか返すだけの 1 行のステートメントで構成されます。 プロパティは、本体が式形式のメンバーとして実装できます。 式本体の定義は、=> 記号の後に、プロパティに割り当てるかプロパティから取得するための式を続けて構成します。

C# 6 以降では、読み取り専用プロパティに get アクセサーを式形式のメンバーとして実装できます。 この場合、get アクセサー キーワードと return キーワードはどちらも使用しません

マイクロソフトのドキュメントより

次のサンプルを見てください

using UnityEngine;

public class Status : MonoBehaviour
{
    public int Life => Life;
}

インテリセンスで変換すると、次のようになります

public class Status
{
    public int Life
    {
        get
        {
            return Life;
        }
    }
}

プロパティの説明

public int Life
{
}

publicのint型のLifeを宣言しています
普通にフィールドの定義をすると public int Life; となりますが、次のブロックがその後続いていますね

get
{
    return xxx;
}


メソッドのようにも見えますが、メソッドだとpublic int Life(); となるはずなのでメソッドでもありません

これは、C#のプロパティ構文になります。Lifeは以降のブロック内を仕切っていると考えましょう

getとは?

この変数の読み込みが発生したときに返す値(xxx)を記述するブロックです
メソッドの戻り値みたいに記述します

public int Life => Life; の構文は何をしているのか?

分解してわかりますが、自分自身を返すことになります
ただ、自分自身を返すときに、またgetが読み込まれるのがまずいですね
ずっと反復して繰り返され永遠に終わりません。その間、returnでの戻り先を記録するためメモリを使いますので無限にメモリを使い、最後に使い切ることになります

結果。OSに止められアプリケーション停止に陥ります

2つのクラスを作って実験してみる

やばいコード

上記をイラストライクに表現してみましょう
C#のオブジェクト指向(クラスとインスタンスの関係)の知識が必要です
なんとなく、ヤバそうなのはわかるでしょうか?

正しいコード

これだと、繰り返し(ループ)は発生しませんね

プロパティは、本来、内部のデータを守ることが目的なのでこのように考えます

Unityでの実行サンプル

目的を少し持たせるためにコード行が増えています
インスペクタにStatusを表示させる等の拡張をすると、なおいいですがここでの目的から外れすぎるので略します

シーン構成

プロジェクトPropertySampleを作成します

次を見て自分で再現してみましょう

スクリプト

読み出し側のコード

using UnityEngine;

public class ViewController : MonoBehaviour
{
    // クラス内のStartメソッドとGetHpメソッドでstatusフィールドを使いたいので
    // スコープを考えて、ここに記述
    Status status;

    void Start()
    {
        // Statusクラスを元にインスタンスを作成します
        status = new Status();

        // 今のLifeの取得
        int nowLife = GetLife();

        // 表示処理のシミュレート
        Debug.Log($"PlayerHp = {nowLife}");
    }

    int GetLife()
    {
        // Lifeの読み込み
        // このコードは、フィールド値の読み込みとプロパティの読み込みで共通
        return status.Life;
    }
}

やばいステータスクラスのコード

これで実行すると、アプリケーションが落ちますが、一度は経験しておくのもいいでしょう

public class Status
{
    // プロパティです
    // アクセス修飾子がpublicなので外部からアクセスできます
    public int Life
    {
        // getブロックは、読み込まれたときに返すデータを記述します
        get
        {
            return Life;
        }
    }
}

正しいステータスクラスのコード

public class Status
{
    // アクセス修飾子が省略されているのでprivate
    // 外部からのアクセスはできません
    // 容易に変更させないためです
    int life = 10;

    // プロパティです
    // アクセス修飾子がpublicなので外部からアクセスできます
    public int Life
    {
        // getブロックは、読み込まれたときに返すデータを記述します
        get
        {
            return life;
        }
    }
}

プロパティの省略を使って、かつコメントも省略すると次のように短くなります
このようなサンプルに出会っても理解できるようにしておきましょう(慣れるのに時間がかかります。徐々にでいいのでパターンを掴んでいきましょう)

public class Status
{
    int life = 10;
    public int Life => life;
}

まとめ

チュートリアルを入力するとき、無心になっていると Life =>Lifeとやってしまい理由を理解していない(または、苦い経験がない場合)には、パニックになります

ですが、理由がわからない経験値のときには、間違ったことにより学習できるタイミングを得られる方がスキルアップにつながるのも事実ですね

C#,Unity,学習

Posted by hidepon