LoginSignup
34
35

More than 5 years have passed since last update.

ジェネリックインターフェースの様々な使い方の例

Last updated at Posted at 2015-07-05

インターフェースと言えばジェネリックの花形で、その定義から実装、利用まで含めて様々なパターンがあります。
しかし、ジェネリックを解説しているサイトなどでは大体が「抽象的無意味な例」しか載せていないように思えます。
そこで、かなり単純化した例ではありますが、一応そこそこ具体的な意味を持った例を考えてみました。

ジェネリックインターフェースの例

とりあえず、ジェネリックインターフェースを定義してみます。
この頁で使うインターフェースはこれだけです。

インターフェース定義
    public interface IGenerator<T>
    {
        T Generate();
    }

型Tの変数を生み出し続けるインターフェースです。
IEnumerable<T>と似ていますが、無限に生み出すところが違います。

普通の実装例

型パラメータTをintとして実装した例です。

実装例
    public class NumberMachine : IGenerator<int>
    {
        int i = 0;

        public int Generate()
        {
            return ++i;
        }
    }

整数を無限に生成します。

型パラメータがさらにジェネリックな実装例

<>の中にそのまた<>があるやつです。

実装例
    public class NumberMachineGenerator : IGenerator<IGenerator<int>>
    {

        public IGenerator<int> Generate()
        {
            return new NumberMachine();
        }
    }

上で定義したNumberMachineを無限に生成します。
適当な利用例を載せておきます。

利用例
var nm_generator = new NumberMachineGenerator();
var generators = new List<IGenerator<int>>();
for(int i = 0; i < 5; i++){
    generators.Add(nm_generator.Generate());
}
int j = 1;
foreach(var g in generators){
    for(int i = 0; i < j; i++){
        Console.WriteLine(g.Generate());
    }
    j++;
}

ここで重要なのは、NumberMachineGeneratorがGenerateするのはあくまでIGenerator<int>であり、NumberMachineとしては認識されないということです。
後でその違いを説明します。

ジェネリックのままでの実装例

型パラメータのTを保ったまま実装する例です。

実装例
    public class NewGenerator<T> : IGenerator<T>
       where T : new()
    {
        public T Generate()
        {
            return new T();
        }
    }

今回は型制約も付けました。引数無しのコンストラクタを持つTに適用でき、そのコンストラクタを使ってTを生み出し続けるクラスです。

型制約に型パラメータを使用する実装例

型パラメータが二つであり、片方のパラメータがもう片方のパラメータの制約に使われる例です。

実装例
    public class GeneratorGenerator<T, U> : IGenerator<T>
        where T : IGenerator<U>, new()
    {
        public T Generate()
        {
            return new T();
        }
    }

IGeneratorを無限に生み出すIGeneratorです。つまり、このクラス自体がIGenerator<IGenerator<U>>を持っていることになります。

簡単な利用例を載せたいのですが、ちょうど最初に実装したNumberMachineが使えそうです。

実装例再掲
    public class NumberMachine : IGenerator<int>
    {
        int i = 0;

        public int Generate()
        {
            return ++i;
        }
    }

デフォルトコンストラクタがnew()制約を満たします。

利用例
var gg = new GeneratorGenerator<NumberMachine, int>();
var generators = new List<NumberMachine>();
for(int i = 0; i < 5; i++){
    generators.Add(gg.Generate());
}
int j = 1;
foreach(var g in generators){
    for(int i = 0; i < j; i++){
        Console.WriteLine(g.Generate());
    }
    j++;
}

最初の利用例と同じ結果になるので、確認してください。
ここでポイントとなるのは、今回はGeneratorGeneratorによってGenerateされるのがちゃんとNumberMachineであると認識されているところです。したがって、NumberMachine独自のメソッドなどがあればそれも使えます。最初の利用例では、それは不可能です。

型パラメータを簡潔に書けないか?

一見、次のようにできそうです。

    public class GeneratorGenerator<U> : IGenerator<IGenerator<U>>
    {
        public IGenerator<U> Generate()
        {
            return new IGenerator<U>(); //成立しない
        }
    }

しかし、これはnew IGenerator<U>()という命令が成立しないためコンパイルエラーですね。

複数の型パラメータに制約が付く実装例

もはやジェネリックインターフェースが関係なくなってきましたが、いちおう例を載せておきます。

実装例
    public class NewGeneratorGenerator<T, U> : IGenerator<T>
        where T : NewGenerator<U>, new()
        where U : new()
    {
        public T Generate()
        {
            return new T();
        }
    }

上で定義したNewGeneratorクラス(またはその派生クラス)を無限に生み出すGeneratorです。やはりこのクラスもIGenerator<IGenerator<U>>を持つことになります。
Uに型制約new()が必要な理由は、NewGeneratorの型パラメータとなるための型制約を満たさなければならないからです。

型パラメータを簡潔に書けないか?

これも一見、次のようにできそうです。

    public class GeneratorGenerator<U> : IGenerator<IGenerator<U>>
        where U : new()
    {
        public NewGenerator<U> Generate()
        {
            return new NewGenerator<U>(); //成立はしている
        }
    }

これは一応成立はしています。しかし、Generateの戻り値がNewGenerator<U>になってしまいます。
元々の実装例では、Generateの戻り値はTです。つまり派生クラスもちゃんと派生クラスで認識されることになります。

型パラメータの共変性

Ver4.0で追加された、型パラメータの共変性・反変性についての例です。
IGeneratorはTが戻り値だけに使われているので、共変性を設定できます。

インターフェース定義
    public interface IGenerator<out T>
    {
        T Generate();
    }

共変性はTが参照型の場合のみ使えるので、適当なクラスと継承関係を定義します。

適当なクラス
    public class Human
    {
        public string name;
    }

    public class Worker : Human
    {
        public string job;
    }

IGenerator<Worker>を適当に実装します。

実装例
    public class JohnGenerator:IGenerator<Worker>
    {
        public Worker Generate()
        {
            return new Worker { name = "John", job = "musician" };
        }
    }

ミュージシャンのJohnを無限に生み出すクラスです。
やっと共変性が利用できます。

利用例
IGenerator<Human> jg = new JohnGenerator(); //outを付けていないとここでエラー
Console.WriteLine(jg.Generate().name);

もしIGeneratorの定義でTにoutを付けていなければ、最初の行で「JohnGeneratorをIGenerator<Human>に暗黙的に変換できません」と怒られてしまいます。
共変性のおかげで、型パラメータが派生クラスから基底クラスになる変換が認められます。

34
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
35