ジェネリック インターフェイス (C# プログラミング ガイド)

多くの場合、ジェネリック コレクション クラスのインターフェイスか、コレクション内の項目を表すジェネリック クラスのインターフェイスを定義すると、便利です。 値型に対するボックス化とボックス化解除の操作を回避するには、ジェネリック クラスで IComparable<T> などのジェネリック インターフェイスを使用することをお勧めします。 .NET クラス ライブラリにより、System.Collections.Generic 名前空間のコレクション クラスと共に利用するためのジェネリック インターフェイスがいくつか定義されます。 これらのインターフェイスの詳細については、「ジェネリック インターフェイス」を参照してください。

インターフェイスが型パラメーターの制約として指定される場合、インターフェイスを実装する型のみを利用できます。 GenericList<T> クラスから派生する SortedList<T> クラスを示したのが次のコード サンプルです。 詳細については、「ジェネリックの概要」を参照してください。 SortedList<T> により制約 where T : IComparable<T> が追加されます。 この制約により、SortedList<T>BubbleSort メソッドは、一覧要素でジェネリック CompareTo メソッドを利用できます。 この例では、一覧要素は単純なクラスである Person です。これは IComparable<Person> を実装します。

//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
    protected Node head;
    protected Node current = null;

    // Nested class is also generic on T
    protected class Node
    {
        public Node next;
        private T data;  //T as private member datatype

        public Node(T t)  //T used in non-generic constructor
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data  //T as return type of property
        {
            get { return data; }
            set { data = value; }
        }
    }

    public GenericList()  //constructor
    {
        head = null;
    }

    public void AddHead(T t)  //T as method parameter type
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    // Implementation of the iterator
    public System.Collections.Generic.IEnumerator<T> GetEnumerator()
    {
        Node current = head;
        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    // IEnumerable<T> inherits from IEnumerable, therefore this class
    // must implement both the generic and non-generic versions of
    // GetEnumerator. In most cases, the non-generic method can
    // simply call the generic method.
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
    // A simple, unoptimized sort algorithm that
    // orders list elements from lowest to highest:

    public void BubbleSort()
    {
        if (null == head || null == head.Next)
        {
            return;
        }
        bool swapped;

        do
        {
            Node previous = null;
            Node current = head;
            swapped = false;

            while (current.next != null)
            {
                //  Because we need to call this method, the SortedList
                //  class is constrained on IComparable<T>
                if (current.Data.CompareTo(current.next.Data) > 0)
                {
                    Node tmp = current.next;
                    current.next = current.next.next;
                    tmp.next = current;

                    if (previous == null)
                    {
                        head = tmp;
                    }
                    else
                    {
                        previous.next = tmp;
                    }
                    previous = tmp;
                    swapped = true;
                }
                else
                {
                    previous = current;
                    current = current.next;
                }
            }
        } while (swapped);
    }
}

// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
    string name;
    int age;

    public Person(string s, int i)
    {
        name = s;
        age = i;
    }

    // This will cause list elements to be sorted on age values.
    public int CompareTo(Person p)
    {
        return age - p.age;
    }

    public override string ToString()
    {
        return name + ":" + age;
    }

    // Must implement Equals.
    public bool Equals(Person p)
    {
        return (this.age == p.age);
    }
}

public class Program
{
    public static void Main()
    {
        //Declare and instantiate a new generic SortedList class.
        //Person is the type argument.
        SortedList<Person> list = new SortedList<Person>();

        //Create name and age values to initialize Person objects.
        string[] names =
        [
            "Franscoise",
            "Bill",
            "Li",
            "Sandra",
            "Gunnar",
            "Alok",
            "Hiroyuki",
            "Maria",
            "Alessandro",
            "Raul"
        ];

        int[] ages = [45, 19, 28, 23, 18, 9, 108, 72, 30, 35];

        //Populate the list.
        for (int x = 0; x < 10; x++)
        {
            list.AddHead(new Person(names[x], ages[x]));
        }

        //Print out unsorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with unsorted list");

        //Sort the list.
        list.BubbleSort();

        //Print out sorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with sorted list");
    }
}

次のように、複数のインターフェイスを単一の型で制約として指定できます。

class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}

1 つのインターフェイスで、次のように、複数の型パラメーターを定義できます。

interface IDictionary<K, V>
{
}

クラスに適用される継承規則は、インターフェイスにも適用されます。

interface IMonth<T> { }

interface IJanuary : IMonth<int> { }  //No error
interface IFebruary<T> : IMonth<int> { }  //No error
interface IMarch<T> : IMonth<T> { }    //No error
                                       //interface IApril<T>  : IMonth<T, U> {}  //Error

ジェネリック インターフェイスが共変の場合、つまり、その型パラメーターを戻り値としてのみ利用する場合、ジェネリック インターフェイスは非ジェネリック インターフェイスから継承できます。 .NET クラス ライブラリでは、IEnumerable<T>IEnumerable から継承します。これは、IEnumerable<T>GetEnumerator の戻り値と Current プロパティ ゲッターの T のみを利用するためです。

具象クラスは、次のように、構築されたクローズ型インターフェイスを実装できます。

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

ジェネリック クラスは、クラス パラメーターの一覧からインターフェイスが必要とするすべての引数が提供される限り、ジェネリック インターフェイスや構築されたクローズ型インターフェイスを実装できます。

interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }

class SampleClass1<T> : IBaseInterface1<T> { }          //No error
class SampleClass2<T> : IBaseInterface2<T, string> { }  //No error

メソッドのオーバーロードを制御する規則は、ジェネリック クラス、ジェネリック構造体、ジェネリック インターフェイス内のメソッドの規則と同じです。 詳細については、「ジェネリック メソッド」を参照してください。

C# 11 以降では、インターフェイスで static abstract または static virtual メンバーを宣言できます。 static abstract または static virtual メンバーのどちらかを宣言するインターフェイスは、ほとんどの場合、ジェネリック インターフェイスです。 コンパイラでは、static virtual および static abstract メソッドへの呼び出しをコンパイル時に解決する必要があります。 インターフェイスで宣言された static virtual および static abstract メソッドには、クラスで宣言された virtual または abstract メソッドに似たランタイム ディスパッチ メカニズムがありません。 代わりに、コンパイラでは、コンパイル時に利用できる型情報が使用されます。 通常、これらのメンバーはジェネリック インターフェイスで宣言されます。 さらに、static virtual または static abstract メソッドを宣言するほとんどのインターフェイスでは、型パラメーターの 1 つが宣言されたインターフェイスを実装する必要があることが宣言されます。 その後、コンパイラでは、指定された型引数を使用して、宣言されたメンバーの型を解決します。

関連項目