Unity/C# 抽象クラスと override 入門

2025年10月2日

1ファイル → 分割 → 図解 → 自動テストまで

TL;DR

  • 学べること:abstract / virtual / override、base 呼び出し、new との違い、sealed override、Unity Test Frameworkでのログ検証。
  • 最小実行:CarTestBehaviour を空オブジェクトにアタッチ → 再生 → Consoleでログ確認。
  • 使い分けの軸:子ごとに必ず別実装なら abstract共通の基本実装があり差し替え可にしたいなら virtual
  • 実践:1ファイル版 → 分割版 → 図解(PlantUML) → 自動テスト → つまずきQ&A → 練習課題の順で理解を固める。

1. サンプルコード

// 車クラス(abstractなのでnew Car()するとエラー)
abstract public class Car
{
    public virtual void Run()
    {
        Debug.Log("走るよ!");
    }

    public void Stop()
    {
        Debug.Log("止まるよ!");
    }
}

// スポーツカー
public class SportCar : Car
{
    public override void Run()
    {
        Debug.Log("超早く");
        base.Run();   // 親クラスのメソッドを呼び出す
    }
}

var sportCar = new SportCar();
sportCar.Run();   // 「超早く」「走るよ!」と出力される
sportCar.Stop();  // 「止まるよ!」と出力される

2. 使い分け早見(abstract / virtual / base)

  • 子クラスごとに実装が必ず異なる → abstract(親は実装を持たない“契約”)
  • 共通の既定動作があり、必要時に差し替え可能 → 親で virtual、子で override
  • 親の処理も活かしたい → 子で base.メソッド() を呼ぶ

3. いますぐ動かす:1ファイル最小例(Unity)

注意:Unityでは MonoBehaviour を継承するクラスは「クラス名=ファイル名」です。

例:public class CarTestBehaviour : MonoBehaviour → ファイルは CarTestBehaviour.cs

ファイル名:CarTestBehaviour.cs

using UnityEngine;

abstract class Car
{
    public virtual void Run()
    {
        Debug.Log("走るよ!");
    }

    public void Stop()
    {
        Debug.Log("止まるよ!");
    }
}

class SportCar : Car
{
    public override void Run()
    {
        Debug.Log("超早く");
        base.Run();
    }
}

public class CarTestBehaviour : MonoBehaviour
{
    void Start()
    {
        var sportCar = new SportCar();

        sportCar.Run();   // 「超早く」「走るよ!」
        sportCar.Stop();  // 「止まるよ!」
    }
}

手順:空のGameObjectを作成 → CarTestBehaviour をアタッチ → 再生 → Consoleに出力が出ればOK。


4. 実務向け:クラスごとに分割する

別ファイル化で見通しと再利用性が向上します。別ファイルから参照するクラスは public を付けるのが基本。

ファイル名:Car.cs

using UnityEngine;

public abstract class Car
{
    public virtual void Run()
    {
        Debug.Log("走るよ!");
    }

    public void Stop()
    {
        Debug.Log("止まるよ!");
    }
}

ファイル名:SportCar.cs

using UnityEngine;

public class SportCar : Car
{
    public override void Run()
    {
        Debug.Log("超早く");
        base.Run();
    }
}

ファイル名:CarTestBehaviour.cs

using UnityEngine;

public class CarTestBehaviour : MonoBehaviour
{
    void Start()
    {
        var sportCar = new SportCar();

        sportCar.Run();
        sportCar.Stop();
    }
}

5. 図解(PlantUML:クラス図/シーケンス図+テーマ)


6. override と new の即理解ミニ対比

public class Car
{
    public virtual void Run() { Debug.Log("Car.Run"); }
}

public class CityCar : Car
{
    public new void Run() { Debug.Log("CityCar.Run (new)"); }  // ← 隠蔽(overrideではない)
}

public class RacingCar : Car
{
    public override void Run() { Debug.Log("RacingCar.Run (override)"); } // ← 正しい上書き
}

void Demo()
{
    CityCar cc = new CityCar();
    cc.Run();          // CityCar.Run (new)

    Car asBase1 = cc;
    asBase1.Run();     // Car.Run(親が呼ばれる:new は隠蔽)

    Car asBase2 = new RacingCar();
    asBase2.Run();     // RacingCar.Run(override は多態が効く)
}

要点:親型参照で常に子の実装を使いたいなら override を選ぶ。


7. sealed override の使いどころ

public class SportCar : Car
{
    public sealed override void Run()
    {
        Debug.Log("SportCar 最終版");
        base.Run();
    }
}
// 以降の派生は Run を override不可(API設計・挙動保証に有効)

8. Unity Test Framework(最小ログ検証)

EditMode テストを新規作成して下記を追加。手動実行から自動検証へ。

using NUnit.Framework;
using UnityEngine;

public class CarSpec
{
    [Test]
    public void SportCar_Run_Stop_Logs()
    {
        string logs = "";
        void Capture(string m, string s, LogType t) => logs += m + "\n";
        Application.logMessageReceived += Capture;

        var sc = new SportCar();
        sc.Run();
        sc.Stop();

        Application.logMessageReceived -= Capture;

        StringAssert.Contains("超早く", logs);
        StringAssert.Contains("走るよ!", logs);
        StringAssert.Contains("止まるよ!", logs);
    }
}

9. よくあるつまずきQ&A(Unity特有)

Q. CarTestBehaviour をアタッチできません

A. クラス名=ファイル名になっているか確認(例:CarTestBehaviour.cs)。

Q. 別ファイルの Car が参照できません

A. public が付いているか、名前空間の衝突がないか確認。

Q. new Car() がコンパイルエラーになります

A. Car は abstract。new SportCar() のように具体クラスを生成。

Q. ログが多すぎてビルド負荷が心配

A. 編集時のみログを出したい場合は Conditional 属性で制御:

public static class Log
{
    [System.Diagnostics.Conditional("UNITY_EDITOR")]
    public static void Info(string msg) => Debug.Log(msg);
}

10. 練習課題(5分でできる)

  1. Truck : Car を追加し、Run() を override。base.Run() を呼ぶ版/呼ばない版を比較。
  2. Car に virtual void Honk() を追加し、各派生でクラクションの違いを実装。
  3. List<Car> に SportCar/Truck を混在させ、foreach で Run()/Stop() を一括呼び出し。

11. 期待ログと成功スクショの目安

期待ログ(Console)

超早く
走るよ!
止まるよ!

スクショのキャプション例

再生すると Console に上記3行が表示されれば成功です。


12. まとめ

  • abstract:子に必須実装を課す“契約”。
  • virtual / override:共通実装を配りつつ、必要時に差し替え。
  • base:親の処理も活かすときに呼ぶ。
  • new ではなく override:親型参照でも子の実装を使いたい場合。
  • sealed override:上書きの終点を宣言してAPIの安定性を高める。
  • テストで固める:Unity Test Frameworkでログ検証を自動化。

訪問数 26 回, 今日の訪問数 1回