C#のジェネリックを使おう

C#にはジェネリックという仕組みがあります。

ジェネリックをざっくり説明すると、データ型が異なるだけの同じようなソースコードを1つで書けるようにする仕組みです。

具体的な例

ジェネリックが無い場合

例えば以下のような、2つの値を入れ替えるSwapという関数を作るとします。

        public static void Swap(ref int a, ref int b)
        {
            int c;

            c = a;
            a = b;
            b = c;
        }

int型で定義された引数の値を入れ替えています。

 

ですが、使いたいデータ型はint型とは限らず、double型だったりfloat型だったりするかもしれません。その為double型用やfloat型用に同じような関数を何個も作らなければなりません。

ジェネリックを使った場合

ジェネリックを使えばそれらを1つのソースコードで済ませる事ができます。

        public static void Swap<T>(ref T a, ref T b)
        {
            T c;

            c = a;
            a = b;
            b = c;
        }

メソッド名の後に<>を付けています。

<>で囲ったTという部分は、この「メソッドを使う時に指定しますよ」という意味になります。

使う時は以下のようになります。

Tはdouble型ですよと指定してSwapメソッドを呼び出しています。

    double a = 1.0;
    double b = 2.0;
    Swap<double>(ref a, ref b);

ちなみに、引数のデータ型から型を推論してくれるので<double>の部分を省略する事もできます。

    double a = 1.0;
    double b = 2.0;
    Swap(ref a, ref b);

ジェネリックなクラス

上の例では、メソッドに対して定義しました。

ジェネリックはクラスに対しても定義する事が出来ます。

    public class Array<T>
    {
        private T[] m_Items;

        public void Set(int index, T item)
        {
            m_Items[index] = item;
        }

        public T Get(int index)
        {
            return m_Items[index];
        }
    }

クラス名の後に<>を付けています。

クラス内のメンバ変数やメソッドの引数、戻り値などで利用できます。

 

 

ジェネリックはクラスメソッドだけでなく、インターフェースデリゲートにも使う事ができます。

複数の型を定義する

置き換えたい型の種類は1つだけとは限りません。

ジェネリックは複数の型を定義する事も可能です。

    public class Pair<T1, T2>
    {
        public T1 m_Key;
        public T2 m_Value;
    }

型に対する制約を指定する

where句を指定する事で使用できる型に制約を設ける事が出来ます。

以下の例では、IComparableインターフェースを実装するクラスだけがTに指定できます。

        public static T Max<T>(T a, T b) where T : IComparable<T>
        {
            if (0 < a.CompareTo(b))
                return a;
            else
                return b;
        }

このように、IComparableのみに制約する事で、IComparableインターフェースのメソッドを呼び出す事が可能になります。

制約はインターフェース以外にも以下のようなものがあります。

where T : struct

値の型である必要があります。

Nullable を除く任意の値の型を指定できます。

where T : class

参照型である必要があります。

任意のクラス、インターフェイス、デリゲート、または配列型にも

適用されます。

where T : new()

パラメーターなしのパブリック コンストラクターが必要です。

new() 制約を別の制約と併用する場合、

この制約を最後に指定する必要があります。

where T :

<ベースクラス名>

この型引数は、指定された基底クラスであるか、

そのクラスから派生している必要があります。

where T :

<インターフェース名>

この型引数は、指定されたインターフェイスであるか、

そのインターフェイスを実装している必要があります。

複数のインターフェイス制約を指定することができます。

制約のインターフェイスを汎用的にすることもできます。