다음을 통해 공유


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

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

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

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

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>T의 반환 값과 GetEnumerator 속성의 getter에서만 Current을 사용하기 때문입니다.

구체적인 클래스는 다음과 같이 닫힌 생성된 인터페이스를 구현할 수 있습니다.

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 메서드를 선언하는 대부분의 인터페이스는 형식 매개 변수 중 하나가 선언된 인터페이스를 구현해야 한다고 선언합니다. 그런 다음 컴파일러는 제공된 형식 인수를 사용하여 선언된 멤버의 형식을 확인합니다.

참고하십시오