Interfaces genéricas (Guía de programación de C#)
Actualización: noviembre 2007
A menudo es útil definir interfaces para las clases de colección genéricas o para las clases genéricas que representan los elementos de la colección. Lo preferible para las clases genéricas es utilizar interfaces genéricas, como IComparable<T> en lugar de IComparable, para evitar las operaciones de conversión boxing y unboxing en los tipos de valor. La biblioteca de clases de .NET Framework define varias interfaces genéricas para utilizarlas con las clases de colección del espacio de nombres System.Collections.Generic.
Cuando una interfaz se especifica como restricción en un parámetro de tipo, sólo se pueden utilizar 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 (Guía de programación de C#). SortedList<T> agrega la restricción where T : IComparable<T>. Esto permite al método BubbleSort de SortedList<T> utilizar 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 IEnumerable<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);
}
}
class Program
{
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 = new string[]
{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};
int[] ages = new int[] { 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 contra-variantes, lo que significa que sólo utilizan su parámetro de tipo como valor devuelto. En la biblioteca de clases de .NET Framework, IEnumerable<T> hereda de IEnumerable porque IEnumerable<T> sólo utiliza 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, las estructuras genéricas o las interfaces genéricas. Para obtener más información, vea Métodos genéricos (Guía de programación de C#).
Vea también
Conceptos
Referencia
Introducción a los genéricos (Guía de programación de C#)