Les classes, interfaces, méthodes génériques

 Supposons qu'on veuille écrire une méthode permutant deux nombres entiers. Cette méthode pourrait être la suivante :
1. public static void Echanger1(ref int value1, ref int value2){
2. // on échange les références value1 et value2
3. int temp = value2;
4. value2 = value1;
5. value1 = temp;
6. }

 Maintenant, si on voulait permuter deux références sur des objets Personne, on écrirait :
1. public static void Echanger2(ref Personne value1, ref Personne value2){
2. // on échange les références value1 et value2
3. Personne temp = value2;
4. value2 = value1;
5. value1 = temp;
6. }
Ce qui différencie les deux méthodes, c'est le type T des paramètres : int dans Echanger1, Personne dans Echanger2. Les classes et
interfaces génériques répondent au besoin de méthodes qui ne diffèrent que par le type de certains de leurs paramètres.
Avec une classe générique, la méthode Echanger pourrait être réécrite de la façon suivante :
1. namespace Chap2 {
2. class Generic1<T> {
3. public static void Echanger(ref T value1, ref T value2){
4. // on échange les références value1 et value2
5. T temp = value2;
6. value2 = value1;
7. value1 = temp;
8. }
9. }
10.}
• ligne 2 : la classe Generic1 est paramétrée par un type noté T. On peut lui donner le nom que l'on veut. Ce type T est
ensuite réutilisé dans la classe aux lignes 3 et 5. On dit que la classe Generic1 est une classe générique.
• ligne 3 : définit les deux références sur un type T à permuter
• ligne 5 : la variable temporaire temp a le type T.
Un programme de test de la classe pourrait être le suivant :

1. using System;
2.
3. namespace Chap2 {
4. class Program {
5. static void Main(string[] args) {
6. // int
7. int i1 = 1, i2 = 2;
8. Generic1<int>.Echanger(ref i1, ref i2);
9. Console.WriteLine("i1={0},i2={1}", i1, i2);
10. // string
11. string s1 = "s1", s2 = "s2";
12. Generic1<string>.Echanger(ref s1, ref s2);
13. Console.WriteLine("s1={0},s2={1}", s1, s2);
14. // Personne
15. Personne p1 = new Personne("jean", "clu", 20), p2 = new Personne("pauline", "dard", 55);
16. Generic1<Personne>.Echanger(ref p1, ref p2);
17. Console.WriteLine("p1={0},p2={1}", p1, p2);
18.
19. }
20. }
21.}
• ligne 8 : lorsqu'on utilise une classe générique paramétrée par des types T1, T2, ... ces derniers doivent être "instanciés".
Ligne 8, on utilise la méthode statique  Echanger  du type  Generic1<int>  pour indiquer que les références passées à la
méthode Echanger sont de type int.
• ligne 12 : on utilise la méthode statique Echanger du type Generic1<string> pour indiquer que les références passées à la
méthode Echanger sont de type string.
• ligne 16 : on utilise la méthode statique Echanger du type Generic1<Personne> pour indiquer que les références passées à la
méthode Echanger sont de type Personne.
Les résultats de l'exécution sont les suivants :
1. i1=2,i2=1
2. s1=s2,s2=s1
3. p1=[pauline, dard, 55],p2=[jean, clu, 20]
La méthode Echanger aurait pu également être écrite de la façon suivante :

1. namespace Chap2 {
2. class Generic2 {
3. public static void Echanger<T>(ref T value1, ref T value2){
4. // on échange les références value1 et value2
5. T temp = value2;
6. value2 = value1;
7. value1 = temp;
8. }
9. }
10.}
• ligne 2 : la classe Generic2 n'est plus générique
• ligne 3 : la méthode statique Echanger est générique
Le programme de test devient alors le suivant :
1. using System;
2.
3. namespace Chap2 {
4. class Program2 {
5. static void Main(string[] args) {
6. // int
7. int i1 = 1, i2 = 2;
8. Generic2.Echanger<int>(ref i1, ref i2);
9. Console.WriteLine("i1={0},i2={1}", i1, i2);
10. // string
11. string s1 = "s1", s2 = "s2";
12. Generic2.Echanger<string>(ref s1, ref s2);
13. Console.WriteLine("s1={0},s2={1}", s1, s2);
14. // Personne
15. Personne p1 = new Personne("jean", "clu", 20), p2 = new Personne("pauline", "dard", 55);
16. Generic2.Echanger<Personne>(ref p1, ref p2);
17. Console.WriteLine("p1={0},p2={1}", p1, p2);
18. }
19. }
20.}
• lignes 8, 12 et 16 : on appelle la méthode Echanger en précisant dans <> le type des paramètres. En fait, le compilateur est
capable de déduire d'après le type des paramètres effectifs, la variante de la méthode Echanger à utiliser. Aussi, l'écriture  suivante est-elle légale :
1. Generic2.Echanger(ref i1, ref i2);
2. ...
3. Generic2.Echanger(ref s1, ref s2);
4. ...
5. Generic2.Echanger(ref p1, ref p2);
Lignes 1, 3 et 5 : la variante de la méthode Echanger appelée n'est plus précisée. Le compilateur est capable de la déduire de la nature
des paramètres effectifs utilisés.
On peut mettre des contraintes sur les paramètres génériques :
Considérons la nouvelle méthode générique Echanger suivante :
1. namespace Chap2 {
2. class Generic3 {
3. public static void Echanger<T>(ref T value1, ref T value2) where T : class {
4. // on échange les références value1 et value2
5. T temp = value2;
6. value2 = value1;
7. value1 = temp;
8. }
9. }
10.}
• ligne 3 : on exige que le type T soit une référence (classe, interface)
Considérons le programme de test suivant :
1. using System;
2.
3. namespace Chap2 {
4. class Program4 {
5. static void Main(string[] args) {
6. // int
7. int i1 = 1, i2 = 2;
8. Generic3.Echanger<int>(ref i1, ref i2);
9. Console.WriteLine("i1={0},i2={1}", i1, i2);
10. // string
11. string s1 = "s1", s2 = "s2";
12. Generic3.Echanger(ref s1, ref s2);
13. Console.WriteLine("s1={0},s2={1}", s1, s2);
14. // Personne
15. Personne p1 = new Personne("jean", "clu", 20), p2 = new Personne("pauline", "dard", 55);
16. Generic3.Echanger(ref p1, ref p2);
17. Console.WriteLine("p1={0},p2={1}", p1, p2);
18.
19. }
20. }
21.}
Le compilateur déclare une erreur sur la ligne 8 car le type int n'est pas une classe ou une interface, c'est une structure :

Le compilateur déclare une erreur sur la ligne 8
Le compilateur déclare une erreur sur la ligne 8

Considérons la nouvelle méthode générique Echanger suivante :
1. namespace Chap2 {
2. class Generic4 {
3. public static void Echanger<T>(ref T element1, ref T element2) where T : Interface1 {
4. // on récupère la valeur des 2 éléments
5. int value1 = element1.Value();
6. int value2 = element2.Value();
7. // si 1er élément > 2ième élément, on échange les éléments
8. if (value1 > value2) {
9. T temp = element2;
10. element2 = element1;
11. element1 = temp;
12. }
13. }
14. }
15.}
• ligne 3 : le type T doit implémenter l'interface Interface1. Celle-ci a une méthode Value, utilisée lignes 5 et 6, qui donne la
valeur de l'objet de type T.
• lignes 8-12 : les deux références element1 et element2 ne sont échangées que si la valeur de element1 est supérieure à la valeur
de element2.
L'interface Interface1 est la suivante :
1. namespace Chap2 {
2. interface Interface1 {
3. int Value();
4. }
5. }
Elle est implémentée par la classe Class1 suivante :
1. using System;
2. using System.Threading;
3.
4. namespace Chap2 {
5. class Class1 : Interface1 {
6. // valeur de l'objet
7. private int value;
8.
9. // constructeur
10. public Class1() {
11. // attente 1 ms
12. Thread.Sleep(1);
13. // valeur aléatoire entre 0 et 99
14. value = new Random(DateTime.Now.Millisecond).Next(100);
15. }
16.
17. // accesseur champ privé value
18. public int Value() {
19. return value;
20. }
21.
22. // état de l'instance
23. public override string ToString() {
24. return value.ToString();
25. }
26. }
27.}
• ligne 5 : Class1 implémente l'interface Interface1
• ligne 7 : la valeur d'une instance de Class1
• lignes 10-14 : le champ value est initialisé avec une valeur aléatoire entre 0 et 99
• lignes 18-20 : la méthode Value de l'interface Interface1
• lignes 23-25 : la méthode ToString de la classe
L'interface Interface1 est également implémentée par la classe Class2 :
1. using System;
2.
3. namespace Chap2 {
4. class Class2 : Interface1 {
5. // valeurs de l'objet
6. private int value;
7. private String s;
8.
9. // constructeur
10. public Class2(String s) {
11. this.s = s;
12. value = s.Length;
13. }
14.
15. // accesseur champ privé value
16. public int Value() {
17. return value;
18. }
19.
20. // état de l'instance
21. public override string ToString() {
22. return s;
23. }
24. }
25.}
• ligne 4 : Class2 implémente l'interface Interface1
• ligne 6 : la valeur d'une instance de Class2
• lignes 10-13 : le champ value est initialisé avec la longueur de la chaîne de caractères passée au constructeur
• lignes 16-18 : la méthode Value de l'interface Interface1
• lignes 21-22 : la méthode ToString de la classe
Un programme de test pourrait être le suivant :
1. using System;
2.
3. namespace Chap2 {
4. class Program5 {
5. static void Main(string[] args) {
6. // échanger des instances de type Class1
7. Class1 c1, c2;
8. for (int i = 0; i < 5; i++) {
9. c1 = new Class1();
10. c2 = new Class1();
11. Console.WriteLine("Avant échange --> c1={0},c2={1}", c1, c2);
12. Generic4.Echanger(ref c1, ref c2);
13. Console.WriteLine("Après échange --> c1={0},c2={1}", c1, c2);
14. }
15. // échanger des instances de type Class2
16. Class2 c3, c4;
17. c3 = new Class2("xxxxxxxxxxxxxx");
18. c4 = new Class2("xx");
19. Console.WriteLine("Avant échange --> c3={0},c4={1}", c3, c4);
20. Generic4.Echanger(ref c3, ref c4);
21. Console.WriteLine("Avant échange --> c3={0},c4={1}", c3, c4);
22. }
23. }
24.}
• lignes 8-14 : des instances de type Class1 sont échangées
• lignes 16-22 : des instances de type Class2 sont échangées
Les résultats de l'exécution sont les suivants :
1. Avant échange --> c1=43,c2=79
2. Après échange --> c1=43,c2=79
3. Avant échange --> c1=72,c2=56
4. Après échange --> c1=56,c2=72
5. Avant échange --> c1=92,c2=75
6. Après échange --> c1=75,c2=92
7. Avant échange --> c1=11,c2=47
8. Après échange --> c1=11,c2=47
9. Avant échange --> c1=31,c2=67
10.Après échange --> c1=31,c2=67
11.Avant échange --> c3=xxxxxxxxxxxxxx,c4=xx
12.Après échange --> c3=xx,c4=xxxxxxxxxxxxxx
Pour illustrer la notion d'interface générique, nous allons trier un tableau de personnes d'abord sur leurs noms, puis sur leurs ages.
La méthode qui nous permet de trier un tableau est la méthode statique Sort de la classe Array :

la méthode statique Sort de la classe Array
la méthode statique Sort de la classe Array

On rappelle qu'une méthode statique s'utilise en préfixant la méthode par le nom de la classe et non par celui d'une instance de la
classe. La méthode Sort a différentes signatures (elle est surchargée). Nous utiliserons la signature suivante :
public static void Sort<T>(T[] tableau, IComparer<T> comparateur)
Sort une méthode générique où T désigne un type quelconque. La méthode reçoit deux paramètres :
• T[] tableau : le tableau d'éléments de type T à trier
• IComparer<T> comparateur : une référence d'objet implémentant l'interface IComparer<T>.
IComparer<T> est une interface générique définie comme suit :
1. public interface IComparer<T>{
2. int Compare(T t1, T t2);
3. }
L'interface IComparer<T> n'a qu'une unique méthode. La méthode Compare :
• reçoit en paramètres deux éléments t1 et t2 de type T
• rend 1 si t1>t2, 0 si t1==t2, -1 si t1<t2. C'est au développeur de donner une signification aux opérateurs <, ==, >. Par
exemple, si p1 et p2 sont deux objets Personne, on pourra dire que p1>p2 si le nom de p1 précède le nom de p2 dans
l'ordre alphabétique. On aura alors un tri croissant selon le nom des personnes. Si on veut un tri selon l'âge, on dira que
p1>p2 si l'âge de p1 est supérieur à l'âge de p2.
• pour avoir un tri dans l'ordre décroissant, il suffit d'inverser les résultats +1 et -1
Nous en savons assez pour trier un tabeau de personnes. Le programme est le suivant :
1. using System;
2. using System.Collections.Generic;
3.
4. namespace Chap2 {
5. class Program6 {
6. static void Main(string[] args) {
7. // un tableau de personnes
8. Personne[] personnes1 = { new Personne("claude", "pollon", 25), new Personne("valentine",
"germain", 35), new Personne("paul", "germain", 32) };
9. // affichage
10. Affiche("Tableau à trier", personnes1);
11. // tri selon le nom
12. Array.Sort(personnes1, new CompareNoms());
13. // affichage
14. Affiche("Tableau après le tri selon les nom et prénom", personnes1);
15. // tri selon l'âge
16. Array.Sort(personnes1, new CompareAges());
17. // affichage
18. Affiche("Tableau après le tri selon l'âge", personnes1);
19. }
20.
21. static void Affiche(string texte, Personne[] personnes) {
22. Console.WriteLine(texte.PadRight(50, '-'));
23. foreach (Personne p in personnes) {
24. Console.WriteLine(p);
25. }
26. }
27. }
28.
29. // classe de comparaison des noms et prénoms des personnes
30. class CompareNoms : IComparer<Personne> {
31. public int Compare(Personne p1, Personne p2) {
32. // on compare les noms
33. int i = p1.Nom.CompareTo(p2.Nom);
34. if (i != 0)
35. return i;
36. // égalité des noms - on compare les prénoms
37. return p1.Prenom.CompareTo(p2.Prenom);
38. }
39. }
40.
41. // classe de comparaison des ages des personnes
42. class CompareAges : IComparer<Personne> {
43. public int Compare(Personne p1, Personne p2) {
44. // on compare les ages
45. if (p1.Age > p2.Age)
46. return 1;
47. else if (p1.Age == p2.Age)
48. return 0;
49. else
50. return -1;
51. }
52. }
53.
54.}
• ligne 8 : le tableau de personnes
• ligne 12 : le tri du tableau de personnes selon les nom et prénom. Le 2ième paramètre de la méthode générique Sort est
une instance d'une classe CompareNoms implémentant l'interface générique IComparer<Personne>.
• lignes 30-39 : la classe CompareNoms implémentant l'interface générique IComparer<Personne>.
• lignes 31-38 : implémentation de la méthode générique int CompareTo(T,T) de l'interface IComparer<T>. La méthode utilise
la méthode String.CompareTo, présentée page 21, pour comparer deux chaînes de caractères.
• ligne 16 : le tri du tableau de personnes selon les âges. Le 2ième paramètre de la méthode générique Sort est une instance
d'une classe CompareAges implémentant l'interface générique IComparer<Personne> et définie lignes 42-51.
Les résultats de l'exécution sont les suivants :
1. Tableau à trier-----------------------------------
2. [claude, pollon, 25]
3. [valentine, germain, 35]
4. [paul, germain, 32]
5. Tableau après le tri selon les nom et prénom------
6. [paul, germain, 32]
7. [valentine, germain, 35]
8. [claude, pollon, 25]
9. Tableau après le tri selon l'âge------------------
10.[claude, pollon, 25]
11.[paul, germain, 32]
12.[valentine, germain, 35]

Publié par Drupal french Study