メソッドオーバーロードからジェネリックへ

2024年6月14日

ジェネリックは初歩の学習では触れない分野になります。
すでに定義されているListやDictionary、またUnityでは、GetComponentなどは、使ったことがあるでしょう。

List<型パラメータ>は配列の次に習うことが多いので目にする機会が多いと思います。
ただ、自作のジェネリックとなると意識しないと、作る場面は少ないでしょう。

難しく感じると思いますが、一度練習として自分で作ってみることをお勧めします。
まずは、ジェネリックを使うとどのようなメリットがあるか感じることが大切です。

ジェネリックメソッド

引数の型を変更しても同じ名前のメソッドを呼び出したい

次のコードを見てください。
実用性はほとんどありませんが、引数の型を変えたときでもエラーにならずメソッドを呼び出せるものです。
メソッドオーバーロードを使っています。(すでに習得されていることを前提としていますが・・)

OverloadGeneric.cs(メソッドオーバーロードで実現)

using UnityEngine;

public class OverloadGeneric : MonoBehaviour
{
    void Start()
    {
        Debug.Log(GetTypeName(1));
    }

    string GetTypeName(int value)
    {
        return value.GetType().ToString();
    }

    string GetTypeName(float value)
    {
        return value.GetType().ToString();
    }
}

まず、コードの機能について説明します。
メソッドのシグネチャ(戻り値の型、メソッド名、引数が記述された定義行)を見てみましょう。
戻り値の型が string、引数の型が int であることがわかります。

string GetTypeName(int value)

ブロック(実行文)をみていきましょう

return value.GetType().ToString();

GetType()メソッドは、objectクラス(ルートクラス)のメソッドでインスタンスの型を取得します。
今回は、value が int型 なので、 int が取得されますね。
ToString()メソッドも同じくobjectクラスのメソッドです。文字列に変換してくれます。
なので、文字列 ”int” が戻り値になります。(実際は、.Netの呼称になりますので、”System.Int32″になります。)


int 型の場合を見てきましたが、引数が 1.0f であれば、型が float なので次のメソッドが呼び出されます。引数の方が float とされていますので、同じメソッド名ですが、こちらが呼び出されます。この仕組みがメソッドのオーバーロードです。

string GetTypeName(float value)

困ったこと

今は、int 型と float 型の2つについて対応できるようにしましたが、これがstring型やdouble型、またまた他の型(自作のクラスも型ですね)と色々な型に対応する必要があった場合、どうでしょうか?

ジェネリックメソッドの出番です。
練習なので、インテリセンスに手伝ってもらいましょう。
Debug.Log(GetTypeName(1));のところを次のように変更しましょう。
List<int>のような書き方を真似してみました。
エラーになりますが、構いません。

 Debug.Log(GetTypeName<int>(1));

はい、エラーになりますね。インテリセンスに手伝ってもらいましょう。

候補をみてみると次のようになっています。(VisualStudio for Mac)

候補を選択すると、次のようなコードが生成されます。

private object GetTypeName<T>(T v)
{
    throw new NotImplementedException();
}

throw new NotImplementedException();は、まだメソッドを実装していないことを例外としているところなので、ここは後で入れ替えます。
シグネチャでメソッド名に<T>が追加されています。ここは、呼び出し元では、<int>でしたよね。
Tは、型パラメータ(型自体を引数とします)と呼ばれています。
<int> 以外でも <float><string> またまた <自作のクラス(型)> もありです。
Player型のクラスを作っていれば、<Player> もありです。

では、候補で作成されたメソッドを調整してみましょう。

  • 戻り値の型がobjectになっていますが、これは、今回文字列を戻り値としたいので、 string にします。
  • 仮パラメータの v は、 value に名前を変更します。(これは、見やすくするためでコード上の影響はありません)

調整後、次のようになります。

private string GetTypeName<T>(T value)
{
    return value.GetType().ToString();
}

ジェネリックに変更した後のコード

OverloadGeneric.cs(int型を型パラメータで渡した場合)

using UnityEngine;

public class OverloadGeneric : MonoBehaviour
{
    void Start()
    {
        Debug.Log(GetTypeName<int>(1));
    }

    private string GetTypeName<T>(T value)
    {
        return value.GetType().ToString();
    }
}

なお、<int>の部分にインテリセンスで候補が出ていますね。省略可能のようです。(引数を見て型を推論しています)
なので、

OverloadGeneric.cs(int型を型パラメータで渡した場合。呼び出し元で、型パラメータを省略)

using UnityEngine;

public class OverloadGeneric : MonoBehaviour
{
    void Start()
    {
        Debug.Log(GetTypeName(1));
    }

    private string GetTypeName<T>(T value)
    {
        return value.GetType().ToString();
    }
}

自作のクラスを型パラメータとした場合

自作のPlayerクラスの場合を見てみましょう。

OverloadGeneric.cs(自作クラスのPlayer型を型パラメータで渡した場合)

using UnityEngine;

public class OverloadGeneric : MonoBehaviour
{
    void Start()
    {
        Player player = new Player();

        Debug.Log(GetTypeName(player));
    }

    private string GetTypeName<T>(T value)
    {
        return value.GetType().ToString();
    }
}

class Player
{

}

おまけ
次のコードは、他のサンプルによっては、1つになっているケースがあるので、そのパターンも覚えておきましょう。

Player player = new Player();
Debug.Log(GetTypeName(player));

1つにまとめたもの

Debug.Log(GetTypeName(new Player()));

実行結果

playerインスタンスは、Playerクラスなので、コンソールに文字で表示すると次のようになります。

ジェネリッククラス

それでは、ジェネリッククラスについてサンプルを作成してみましょう。
List<型パラメータ>は、ジェネリッククラスと呼ばれます。

Playerクラス(ジェネリッククラス)

Playerクラスをジェネリックに変更してみましょう。
構文は、次のコードのようになります。
クラス名の後ろに<T>をつけます。
これは、ジェネリックメソッドの時と同じですね。<T>には、様々な型が代入されます。
T playerType; は、例えば、
T に int が代入されれば、 int playerTypeとなり、
Tに float が代入されれば、 float playerTypeとなります。
つまり、playerTypeは型パラメータによって型が決まることになるのです。

playerType.GetType() で、その型の名前が取得されることになります。
int 型なら int でしたよね。

class Player<T>
{
    T playerType;

    public string GetTypeName()
    {
        return playerType.GetType().ToString();
    }
}

OverloadGeneric.cs

メソッドを呼び出すサンプルです。インスタンスを作成するときに、型パラメータを指定しています。

public class OverloadGeneric : MonoBehaviour
{
    void Start()
    {
        Player<float> player = new Player<float>();

        Debug.Log(player.GetTypeName());
    }
}

今回は、シンプルに機能のみ紹介しました。本来、設定する型パラメータの制約を設けたりコンストラクタで、フィールドの内容を書き換えたりして使うことになります。慣れたきたら、いろいろ試してみましょう。

さらに発展された設計

C#,Unity

Posted by hidepon