Bagikan melalui


Antarmuka Generik (Panduan Pemrograman C#)

Seringkali berguna untuk menentukan antarmuka baik untuk kelas koleksi generik, atau untuk kelas generik yang mewakili item dalam koleksi. Untuk menghindari operasi pengotakan dan pembukaan kotak pada jenis nilai, lebih baik menggunakan antarmuka generik, seperti IComparable<T>, pada kelas generik. Pustaka kelas .NET mendefinisikan beberapa antarmuka generik untuk digunakan dengan kelas koleksi di namespace System.Collections.Generic. Untuk informasi selengkapnya tentang antarmuka ini, lihat Antarmuka generik.

Ketika antarmuka ditentukan sebagai batasan pada parameter jenis, hanya jenis yang mengimplementasikan antarmuka tersebut yang dapat digunakan. Contoh kode berikut menunjukkan kelas SortedList<T> yang berasal dari kelas GenericList<T>. Untuk informasi selengkapnya, lihat Pengenalan Generik. SortedList<T> menambahkan batasan where T : IComparable<T>. Batasan ini memungkinkan BubbleSort metode dalam SortedList<T> untuk menggunakan metode generik CompareTo pada elemen daftar. Dalam contoh ini, elemen daftar adalah kelas sederhana, Person yang mengimplementasikan 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");
    }
}

Beberapa antarmuka dapat ditentukan sebagai batasan pada satu jenis, sebagai berikut:

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

Antarmuka dapat menentukan lebih dari satu parameter jenis, sebagai berikut:

interface IDictionary<K, V>
{
}

Aturan pewarisan yang berlaku untuk kelas juga berlaku untuk antarmuka:

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

Antarmuka generik dapat mewarisi dari antarmuka non-generik jika antarmuka generik adalah covariant, yang berarti hanya menggunakan parameter jenisnya sebagai nilai pengembalian. Di pustaka kelas .NET, IEnumerable<T> mewarisi dari IEnumerable karena IEnumerable<T> hanya menggunakan T dalam nilai pengembalian GetEnumerator dan dalam pengambil properti Current.

Kelas konkret dapat menerapkan antarmuka yang dibangun tertutup, sebagai berikut:

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

Kelas generik dapat mengimplementasikan antarmuka generik atau antarmuka yang dibangun tertutup selama daftar parameter kelas memasok semua argumen yang diperlukan oleh antarmuka, sebagai berikut:

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

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

Aturan yang mengontrol kelebihan beban metode sama untuk metode dalam kelas generik, struktur generik, atau antarmuka generik. Untuk informasi selengkapnya, lihat Metode Generik.

Dimulai dengan C# 11, antarmuka dapat mendeklarasikan static abstract atau static virtual anggota. Antarmuka yang mendeklarasikan salah satu static abstract atau static virtual anggota hampir selalu merupakan antarmuka generik. Pengkompilasi harus menyelesaikan panggilan ke static virtual dan static abstract metode pada waktu kompilasi. static virtual metode dan static abstract yang dideklarasikan dalam antarmuka tidak memiliki mekanisme pengiriman runtime yang dianalogikan ke virtual atau abstract metode yang dideklarasikan dalam kelas. Sebagai gantinya, pengkompilasi menggunakan informasi jenis yang tersedia pada waktu kompilasi. Anggota ini biasanya dideklarasikan dalam antarmuka generik. Selain itu, sebagian besar antarmuka yang menyatakan static virtual atau static abstract metode menyatakan bahwa salah satu parameter jenis harus mengimplementasikan antarmuka yang dideklarasikan. Pengkompilasi kemudian menggunakan argumen jenis yang disediakan untuk menyelesaikan jenis anggota yang dideklarasikan.

Lihat juga