Interfaces génériques (Guide de programmation C#)
Mise à jour : novembre 2007
Il est souvent utile de définir des interfaces pour les classes de collection génériques, ou pour les classes génériques qui représentent des éléments dans la collection. Pour les classes génériques, il est préférable d'utiliser des interfaces génériques, telles que IComparable<T> à la place de IComparable, pour éviter les opérations de conversion boxing et unboxing sur les types valeur. La bibliothèque de classes .NET Framework définit plusieurs nouvelles interfaces génériques à utiliser avec les classes de collection dans l'espace de noms System.Collections.Generic.
Lorsqu'une interface est spécifiée comme une contrainte sur un paramètre de type, seuls les types qui implémentent l'interface peuvent être utilisés. L'exemple de code suivant illustre une classe SortedList<T> qui dérive de la classe GenericList<T>. Pour plus d'informations, consultez Introduction aux génériques (Guide de programmation C#). SortedList<T> ajoute la contrainte where T : IComparable<T>. Ceci permet à la méthode BubbleSort dans SortedList<T> d'utiliser la méthode générique CompareTo sur les éléments de liste. Dans cet exemple, les éléments de liste sont une classe simple, Person qui implémente 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");
}
}
Les interfaces multiples peuvent être spécifiées en tant que contraintes sur un seul type, comme suit :
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}
Une interface peut définir plusieurs paramètres de type, comme suit :
interface IDictionary<K, V>
{
}
Les règles d'héritage qui s'appliquent aux classes s'appliquent également aux 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
Les interfaces génériques peuvent hériter d'interfaces non génériques si l'interface générique est de type contravariant, ce qui signifie qu'elle utilise uniquement son paramètre de type comme valeur de retour. Dans la bibliothèque de classes .NET Framework, IEnumerable<T> hérite de IEnumerable, car IEnumerable<T> utilise uniquement T dans la valeur de retour de GetEnumerator et dans l'accesseur Get de propriété Current.
Les classes concrètes peuvent implémenter des interfaces construites fermées, comme suit :
interface IBaseInterface<T> { }
class SampleClass : IBaseInterface<string> { }
Les classes génériques peuvent implémenter des interfaces génériques ou des interfaces construites fermées tant que la liste de paramètres de classe fournit tous les arguments requis par l'interface, comme suit :
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
class SampleClass1<T> : IBaseInterface1<T> { } //No error
class SampleClass2<T> : IBaseInterface2<T, string> { } //No error
Les règles qui déterminent la surcharge des méthodes sont les mêmes pour les méthodes dans les classes génériques, les structs génériques ou les interfaces génériques. Pour plus d'informations, consultez Méthodes génériques (Guide de programmation C#).
Voir aussi
Concepts
Référence
Introduction aux génériques (Guide de programmation C#)