Поделиться через


Универсальные интерфейсы (руководство по программированию на C#)

Часто рекомендуется определять интерфейсы для универсальных классов коллекций или универсальных классов, представляющих элементы в коллекции. Чтобы избежать операций коробки и распаковки с типами значений, лучше использовать обобщенные интерфейсы, такие как 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 и в геттере свойства 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 virtual и static abstract методы во время компиляции. static virtual и static abstract методы, объявленные в интерфейсах, не имеют механизма диспетчеризации среды выполнения, аналогичного virtual методам, abstract объявленным в классах. Вместо этого компилятор использует сведения о типе, доступные во время компиляции. Обычно эти члены объявляются в универсальных интерфейсах. Кроме того, большинство интерфейсов, объявляющих или методы, static virtual объявляют, что один из параметров типа должен static abstract. Затем компилятор использует предоставленные аргументы типа для разрешения типа объявленного члена.

См. также