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

A menudo es útil definir interfaces para las 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. En la biblioteca de clases de .NET se definen varias interfaces genéricas para usarlas con las clases de colección del espacio de nombres System.Collections.Generic. Para más información sobre estas interfaces, consulte Interfaces genéricas.

Cuando una interfaz se especifica como restricción en un parámetro de tipo, solo se pueden usar los tipos que implementan la interfaz. El ejemplo de código siguiente muestra una clase SortedList<T> derivada de la clase GenericList<T>. 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 método BubbleSort de SortedList<T> usar el método CompareTo genérico con los 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, de la siguiente manera:

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

Una interfaz puede definir más de un parámetro de tipo, de la siguiente manera:

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 son covariantes, lo que significa que solo usan 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 propiedad Current.

Las clases concretas pueden implementar interfaces construidas cerradas, de la siguiente manera:

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 la clase suministre todos los argumentos que necesita la interfaz, de la siguiente manera:

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 incluidos en las clases genéricas, los structs genéricos o las interfaces genéricas. Para obtener más información, vea Métodos genéricos.

A partir de C# 11, las interfaces pueden declarar miembros static abstract o static virtual. Las interfaces que declaran miembros static abstract o static virtual 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. Los métodos static virtual y static abstract declarados en interfaces no tienen un mecanismo de distribución en tiempo de ejecución análogo a los métodos virtual o abstract declarados en clases. En su lugar, el compilador usa la información de tipos disponible en tiempo de compilación. Estos miembros se declaran normalmente 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