Compartir a través de


Interfaces genéricas (Guía de programación de C#)

A menudo resulta útil definir interfaces para clases de colección genéricas o para las clases genéricas que representan elementos de la colección. Para evitar operaciones de conversión boxing y unboxing en tipos de valor, es mejor usar interfaces genéricas, como IComparable<T>, en clases genéricas. La biblioteca de clases de .NET define varias interfaces genéricas para su uso con las clases de colección en el System.Collections.Generic espacio de nombres . Para obtener más información sobre estas interfaces, consulte Interfaces genéricas.

Cuando se especifica una interfaz como restricción en un parámetro de tipo, solo se pueden usar los tipos que implementan la interfaz. En el ejemplo de código siguiente se muestra una SortedList<T> clase que deriva de la GenericList<T> clase . Para obtener más información, vea Introducción a los genéricos. SortedList<T> agrega la restricción where T : IComparable<T>. Esta restricción permite al BubbleSort método de SortedList<T> usar el método genérico CompareTo en elementos de lista. En este ejemplo, los elementos de lista son una clase simple, 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");
    }
}

Se pueden especificar varias interfaces como restricciones en un solo tipo, como se indica a continuación:

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

Una interfaz puede definir más de un parámetro de tipo, como se indica a continuación:

interface IDictionary<K, V>
{
}

Las reglas de herencia que se aplican a las clases también se aplican a las 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

Las interfaces genéricas pueden heredar de interfaces no genéricas si la interfaz genérica es covariante, lo que significa que solo usa su parámetro de tipo como valor devuelto. En la biblioteca de clases de .NET, IEnumerable<T> hereda de IEnumerable porque IEnumerable<T> solo usa T en el valor devuelto de GetEnumerator y en el captador de propiedades Current .

Las clases concretas pueden implementar interfaces construidas cerradas, como se indica a continuación:

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

Las clases genéricas pueden implementar interfaces genéricas o interfaces construidas cerradas siempre que la lista de parámetros de clase proporciona todos los argumentos requeridos por la interfaz, como se indica a continuación:

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

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

Las reglas que controlan la sobrecarga de métodos son las mismas para los métodos dentro de clases genéricas, estructuras genéricas o interfaces genéricas. Para obtener más información, vea Métodos genéricos.

A partir de C# 11, las interfaces pueden declarar static abstract o static virtual miembros. Las interfaces que declaran o static abstractstatic virtual miembros son casi siempre interfaces genéricas. El compilador debe resolver las llamadas a los métodos static virtual y static abstract en tiempo de compilación. static virtual Los métodos y static abstract declarados en interfaces no tienen un mecanismo de distribución en tiempo de ejecución análogo a virtual los métodos o abstract declarados en clases. En su lugar, el compilador usa la información de tipos disponible en tiempo de compilación. Estos miembros normalmente se declaran en interfaces genéricas. Además, la mayoría de las interfaces que declaran métodos static virtual o static abstract declaran que uno de los parámetros de tipo debe implementar la interfaz declarada. A continuación, el compilador usa los argumentos de tipo proporcionados para resolver el tipo del miembro declarado.

Consulte también