Partilhar via


Interfaces genéricas (Guia de programação em C#)

Muitas vezes, é útil definir interfaces para classes de coleção genéricas ou para as classes genéricas que representam itens na coleção. Para evitar operações de boxe e unboxing em tipos de valor, é melhor usar interfaces genéricas, como IComparable<T>, em classes genéricas. A biblioteca de classes .NET define várias interfaces genéricas para uso com as classes de coleção no System.Collections.Generic namespace. Para obter mais informações sobre essas interfaces, consulte Interfaces genéricas.

Quando uma interface é especificada como uma restrição em um parâmetro type, somente os tipos que implementam a interface podem ser usados. O exemplo de código a seguir mostra uma SortedList<T> classe que deriva da GenericList<T> classe. Para obter mais informações, consulte Introdução aos genéricos. SortedList<T> adiciona a restrição where T : IComparable<T>. Essa restrição permite que o BubbleSort método em SortedList<T> use o método genérico CompareTo em elementos de lista. Neste exemplo, os elementos de lista são uma classe simples, Person que implementa 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");
    }
}

Várias interfaces podem ser especificadas como restrições em um único tipo, da seguinte maneira:

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

Uma interface pode definir mais de um parâmetro de tipo, da seguinte maneira:

interface IDictionary<K, V>
{
}

As regras de herança que se aplicam às classes também se aplicam às interfaces:

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

As interfaces genéricas podem herdar de interfaces não genéricas se a interface genérica for covariante, o que significa que ela usa apenas seu parâmetro type como um valor de retorno. Na biblioteca de classes .NET, IEnumerable<T> herda de IEnumerable porque IEnumerable<T> só usa T no valor de retorno de e no Current getter de GetEnumerator propriedade.

As classes de concreto podem implementar interfaces construídas fechadas, da seguinte forma:

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

Classes genéricas podem implementar interfaces genéricas ou interfaces construídas fechadas, desde que a lista de parâmetros de classe forneça todos os argumentos exigidos pela interface, da seguinte maneira:

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

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

As regras que controlam a sobrecarga de método são as mesmas para métodos dentro de classes genéricas, estruturas genéricas ou interfaces genéricas. Para obter mais informações, consulte Métodos genéricos.

A partir do C# 11, as interfaces podem declarar static abstract ou static virtual membros. As interfaces que declaram um ou static abstractstatic virtual membros são quase sempre interfaces genéricas. O compilador deve resolver chamadas e static abstract métodos em static virtual tempo de compilação. static virtual e static abstract os métodos declarados em interfaces não têm um mecanismo de despacho de tempo de execução análogo ou virtualabstract métodos declarados em classes. Em vez disso, o compilador usa informações de tipo disponíveis em tempo de compilação. Esses membros são normalmente declarados em interfaces genéricas. Além disso, a maioria das interfaces que declaram static virtual ou static abstract métodos declaram que um dos parâmetros de tipo deve implementar a interface declarada. O compilador, em seguida, usa os argumentos de tipo fornecidos para resolver o tipo do membro declarado.

Consulte também