読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【C#】【Unity】継承とInterfaceに触れてみた

はじめに

UnityとC#を再学習中です。 さんざんコード書いておいて今更感はありますが、前から気になっていた継承とInterfaceについて調べてみました。

以前も使用した、Assets以下に置いた画像ファイルを「ReadAllBytes」と「LoadImage」を使って、 マルチスレッドで読み込もうとしたら見事に失敗したので、その腹いせでもありますw
(MonoBehaviourを継承していなければマルチスレッドが使用できるのですが、「LoadImage」はメインスレッドでしか動かないとのことでした。これについては継続してうまい方法を考えてみます)

最後に参考リンクをまとめますが、継承もInterfaceも、ググれば有用な情報がたくさん手に入ります。 なので、ここではこの2つの要素について、なんとなく私が理解した内容や、(Unityで)自分ならこう使うかな、ということをまとめておきたいと思います。

継承

まずは継承から。親となるクラスの継承やメソッド、メンバ変数をそのまま引き継ぐことができます。

ParentBase.cs

using UnityEngine;

public class ParentBase : MonoBehaviour
{
    private int intPersonalNum = 0;
    public void Output()
    {
        Debug.Log(intPersonalNum);
        Debug.Log(this.GetRect());
    }
    private Rect GetRect()
    {
        var rctNewRect = new Rect(0, 0, 320, 240);
        return rctNewRect;
    } 
}

この親クラスを継承します。

CtrlChild1.cs

using UnityEngine;

public class CtrlChild1 : ParentBase
{
    public void OutputOnCtrlChild1()
    {
        // 独自メソッド.
        Debug.Log("Child1");
    }
}

で、これをメインクラスから呼び出してみます。

CtrlMain.cs

using UnityEngine;

public class CtrlMain: MonoBehaviour {

    private CtrlChild1 ctrChild1;
    public GameObject gmoChild1;
    
    private void Start()
    {
        ctrChild1 = gmoChild1.GetComponent();
        // ParentBaseから継承したメソッドの呼び出し.
        ctrChild1.Output();
        // 独自メソッドの呼び出し.
        ctrChild1.OutputOnCtrlChild1();
    }
}

CtrlChild1.csでは実装していないメソッドや変数も、継承元のParentBase.csで実装しているため呼び出すことができます。
親クラスのメソッドをオーバーライドして、独自のふるまいをさせることも可能です。

ParentBase.cs

~省略~
    // 子クラスでオーバーライドさせるためにはvirtualやabstractなどの修飾子を付与する必要がある.
    public virtual void Output()
    {
        Debug.Log(intPersonalNum);
        Debug.Log(this.GetRect());
    }
~省略~  

CtrlChild1.cs

~省略~
    public override void Output()
    {
        // 親クラスのメソッドをオーバーライドする.
        Debug.Log("Overridden");
    }
~省略~

使い方

UnityEditorからC#スクリプトを作成すると、標準で「MonoBehaviour」を継承したクラスが作成されるので、ある意味お馴染み?
親クラスで「MonoBehaviour」を継承しておけば、子クラスも通常通りGameObjectにアタッチできます。
複数のScriptで、全く同じメソッドや処理をさせたいとき(はっきりと思いつかないのですが。。。)に使えそうです。

また、上記のCtrlMain.csでの呼び出し時、「private CtrlChild1 ctrChild1;」ではなく親クラスの「private ParentBase ctrChild1;」としても、同じようにCtrlChild1.csをアタッチして使用することができます。
(※ただし子クラスで独自実装したメソッドは使用不可)

下記のInterfaceとともに、例えば実行中のSceneによってほぼ同じ内容で微妙に異なるScriptを使いたい場合、以下のようにできる、ということですね。

private ParentBase ctrChild1;
private ParentBase ctrChild2;
private ParentBase currentChild = (Application.loadedLevelName == "Scene1")? ctrChild1: ctrChild2;

呼び出し元のScriptが一つにまとめられるだけでも、少しスッキリする気がします。

Interface

こちらは親を子クラスが継承する、というところは同じですが、いくつか特徴があります。

  • 複数の継承が可能(継承の場合は1つのみ)
  • Interface自身はクラスを継承することはできない
  • Interface自身は実装を持たない
  • 子クラスはInterfaceが持つメソッド、プロパティなどをすべて実装する必要がある
  • メソッドやプロパティなどはすべてpublicかつabstractとして扱われる

Iparent.cs

interface Iparent{
    void Initialize();
    void UpdateStatus();
    string StrRoleName { get; set; }
}

CtrlChild2.cs

using UnityEngine;

public class CtrlChild2 : MonoBehaviour, Iparent
{
    void Start () {
    
    }
    void Update () {
    
    }
    public void Initialize()
    {
        Debug.Log("Child2 init");
    }
    public void UpdateStatus()
    {
        Debug.Log("Child2 update");
    }
    public string StrRoleName {
        get; set;
    }
}

使い方

Interface自身はクラスを継承できないこと、また子クラスは複数のInterfaceを継承できることから、「MonoBehaviour」も継承し、GameObjectにアタッチできるようにしています

また、メソッドの実装部分は子クラスごとに行われるため、継承のところで書いた「実行中のSceneによってほぼ同じ内容で微妙に異なるScriptを使いたい場合」はInterfaceを使って、 「Iparent」を型として子クラスを呼び分けたいときに使えそうです。

C# 実践開発手法ではクラス間の依存関係を取り除くため、Interfaceが使われていました。
今回は触れられませんでしたが、次回以降試してみたいと思います。

終わりに

まだぼんやりした理解でしかありませんが、うまく使いこなしてより簡潔にコードが書けるようになりたいものです(小並感)

参考

継承

インターフェイス

書籍

Unity