제네릭 인터페이스(C# 프로그래밍 가이드)

제네릭 컬렉션 클래스에 대한 인터페이스 또는 컬렉션의 항목을 나타내는 제네릭 클래스에 대한 인터페이스를 정의하는 것이 대개 유용합니다. 값 형식에 대한 boxing 및 unboxing 작업을 피하려면 제네릭 클래스에서 IComparable<T>와 같은 제네릭 인터페이스를 사용하는 것이 좋습니다. .NET 클래스 라이브러리에는 System.Collections.Generic 네임스페이스의 컬렉션 클래스에 사용할 제네릭 인터페이스가 여러 개 정의되어 있습니다. 이러한 인터페이스에 대한 자세한 내용은 제네릭 인터페이스를 참조하세요.

인터페이스를 형식 매개 변수에 대한 제약 조건으로 지정한 경우 이 인터페이스를 구현하는 형식만 사용할 수 있습니다. 다음 코드 예제는 GenericList<T> 클래스에서 파생되는 SortedList<T> 클래스를 보여 줍니다. 자세한 내용은 제네릭 소개를 참조하세요. SortedList<T>where T : IComparable<T> 제약 조건을 추가합니다. 이 제약 조건을 사용하면 SortedList<T>BubbleSort 메서드가 목록 요소에 제네릭 CompareTo 메서드를 사용할 수 있습니다. 이 예제에서 목록 요소는 IComparable<Person>을 구현하는 단순 클래스인 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>
{
}

하나의 인터페이스는 다음과 같은 두 개 이상의 형식 매개 변수를 정의할 수 있습니다.

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

제네릭 인터페이스가 공변(covariant)인 경우, 제네릭 인터페이스는 제네릭이 아닌 인터페이스에서 상속할 수 있습니다. 즉, 제네릭 인터페이스는 형식 매개 변수만 반환 값으로 사용합니다. .NET 클래스 라이브러리에서 IEnumerable<T>IEnumerable에서 상속받습니다. IEnumerable<T>GetEnumerator의 반환 값과 Current 속성 getter에 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 virtualstatic abstract 메서드에 대한 호출을 확인해야 합니다. 인터페이스에 선언된 static virtualstatic abstract 메서드에는 클래스에 선언된 virtual 또는 abstract 메서드와 유사한 런타임 디스패치 메커니즘이 없습니다. 대신 컴파일러는 컴파일 시간에 사용 가능한 형식 정보를 사용합니다. 이러한 멤버는 일반적으로 제네릭 인터페이스에서 선언됩니다. 또한 static virtual 또는 static abstract 메서드를 선언하는 대부분의 인터페이스는 형식 매개 변수 중 하나가 선언된 인터페이스를 구현해야 한다고 선언합니다. 그런 다음 컴파일러는 제공된 형식 인수를 사용하여 선언된 멤버의 형식을 확인합니다.

참고 항목