ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

C# - ポリモーフィズム(多態性、多様性)

初めに

 オブジェクト指向プログラミングの3つの柱として、「カプセル化」、「継承」、「ポリモーフィズム」がよく挙げられます。本記事では、ポリモーフィズム多態性、多様性)について、説明します。


前提知識

 ポリモーフィズムを理解するためには、継承の知識が必要になります。継承については、下のリンク先で説明していますので、そちらをご覧ください。
fineworks-fine.hatenablog.com

ポリモーフィズム(polymorphism:多態性、多様性)

ポリモーフィズムとは

 ポリモーフィズムとは、

ある関数の呼び出しに対し、オブジェクトごとに異なる動作をすること

を言います。

 例えば、Animal という基底クラスから Dog という派生クラスと Cat という派生クラスを作ったとします。「鳴き声を取得する」など、ある関数を呼び出したとき、Dog と Cat では別の動作をさせることがポリモーフィズムになります。

静的な型、動的な型

 継承で説明したように、派生クラスのインスタンスは基底クラスとして扱うことができます。

class Animal{}  //基底クラス
class Dog : Animal{}  //派生クラス

//基底クラスのインスタンスは基底クラスとして扱える
Animal a1 = new Animal();
//派生クラスのインスタンスは派生クラスとして扱える
Dog d1 = new Dog();

//派生クラスのインスタンスは基底クラスとしても扱える
Animal a2 = new Dog();
//基底クラスのインスタンスは派生クラスとして扱えない
Dog d2 = new Animal();

 上のコードのように、派生クラスのインスタンスは基底クラスとしても扱える(基底クラスの変数の型に代入できる)ため、変数の型が左辺と右辺で異なる場合があります。この場合、左辺と右辺の型をそれぞれ次のようにいいます。

静的な型:代入される変数の型(左辺)

動的な型:代入するインスタンスの型(右辺)

//静的な型がAnimal
//動的な型がdog
Animal a2 = new Dog();
仮想メソッド(vertual、override)

 通常の場合、基底クラスと派生クラスに同名の関数がある場合、静的な型(左辺)の関数が呼び出されます。

using UnityEngine;
using System;

public class DeriveSample : MonoBehaviour
{
    void Start()
    {
        //派生クラスのインスタンスを基底クラスに格納
        Base a = new Derived();
        a.Test();  //Base が表示される
        //基底クラスのインスタンスを基底クラスに格納
        Base b = new Base();
        b.Test();  //Base が表示される
        //派生クラスのインスタンスを派生クラスに格納
        Derived c = new Derived();
        c.Test();  //Derived が表示される
    }
}

//基底クラスの定義
class Base
{
    public void Test()  //派生クラスと同名の関数
    {
        Debug.Log("Base");
    }
}

//派生クラスの定義
class Derived : Base
{
    public new void Test()  //基底クラスと同名の関数
    {
        Debug.Log("Derived");
    }
}

 しかし、派生クラスごとに関数に異なる動作をさせたい場合など、動的な型の関数が呼び出されたほうが都合がよい場合が多いです。

 動的な型に基づいて関数を選ぶ場合、次のように virtual 修飾子と override 修飾子を使います。

  • 基底クラスの関数に virtual 修飾子をつけ、仮想メソッドにする
  • 派生クラスで同名の関数に override 修飾子をつけ、仮想メソッドのオーバーライド(再定義)する

using UnityEngine;
using System;

public class DeriveSample : MonoBehaviour
{
    void Start()
    {
        //派生クラスのインスタンスを基底クラスに格納
        Base a = new Derived();
        a.Test();  //Derived が表示される
        //基底クラスのインスタンスを基底クラスに格納
        Base b = new Base();
        b.Test();  //Base が表示される
        //派生クラスのインスタンスを派生クラスに格納
        Derived c = new Derived();
        c.Test();  //Derived が表示される
    }
}

//基底クラスの定義
class Base
{
    //仮想メソッドにする
    public virtual void Test()  //派生クラスと同名の関数
    {
        Debug.Log("Base");
    }
}

//派生クラスの定義
class Derived : Base
{
    //オーバーライドする
    public override void Test()  //基底クラスと同名の関数
    {
        Debug.Log("Derived");
    }
}
サンプル

 "Pokemon" という基底クラスと "Pikachu" という派生クラスを作り、名前とタイプを派生クラスごとに取得できるようにしました。

using UnityEngine;
using System;

public class DeriveSample : MonoBehaviour
{
    void Start()
    {
        Pokemon pika = new Pikachu();
        Pokedex(pika);
    }

    public static void Pokedex(Pokemon p)
    {
        Debug.Log($"Name:{p.Name}, Type:{p.Type}");
    }
}

//基底クラス
public class Pokemon
{
    string name;
    string type;

    //コンストラクタ
    public Pokemon() { }

    //仮想メソッド
    public virtual string Name { get { return "名前"; } }
    public virtual string Type { get { return "タイプ"; } }
}

//派生クラス
public class Pikachu : Pokemon
{
    //コンストラクタ
    public Pikachu() { }
    //オーバーライド
    public override string Name
    {
        get
        {
            return "pikachu";
        }
    }

    public override string Type
    {
        get
        {
            return "でんき";
        }
    }
}

 サンプルコードの中で get 修飾子を使っています。使い方は下のリンク先をご覧ください。
fineworks-fine.hatenablog.com

戻り値の共変性(C#9.0)

 C#9.0以前は、オーバーライド元の仮想メソッドとオーバーライドした関数の返り値の型は同じ必要がありました。しかし、C#9.0からクラスの共変戻り値が機能として追加され、関数のオーバーライドで返り値の型を元の返り値の派生クラスの型にできるようになりました。

public class Pokemon
{
    public virtual Pokemon Clone()
    {
        return new Pokemon();
    }
}

public class Pikachu : Pokemon
{
    //基底クラスでのClone()の戻り値はPokemonであるため、今までは戻り値はPokemonにする必要があった
    //C#9.0では、派生クラスの戻り値の型を取ることができる
    public override Pikachu Clone()
    {
        return new Pikachu();
    }
}

(補足:Unity のバージョンと C# のバージョンについて)
 Unity で C#9.0 に対応したのが、2021.2 からのようです。それ以前のバージョンで使うとエラーがでるのでご注意ください。
docs.unity3d.com

最後に

 ポリモーフィズムはある関数をオブジェクトごとに異なる動作をさせるオブジェクト指向プログラミングの概念の一つ。virtual を使い、関数を仮想メソッドにし、派生クラスで override することで派生クラスごとに動作を分けることができる。