Université de Gafsa
Institut Supérieur d'Administration des Entreprises de Gafsa
Framework .NET (C#)
Chapitre 2 : Les collections et structures de données génériques
Enseignant : Boubaker KHMILI
Public : 2ème année Licence Business Computing
Année universitaire 2026/2027

Objectifs du chapitre

À l'issue de ce chapitre, l'étudiant sera capable de :

  1. Expliquer la différence entre les collections génériques et non génériques
  2. Déclarer, manipuler et parcourir une List<T>
  3. Utiliser un Dictionary<TKey, TValue> pour stocker des paires clé-valeur
  4. Appliquer un HashSet<T> pour gérer des ensembles de valeurs uniques
  5. Implémenter une file d'attente avec Queue<T>
  6. Implémenter une pile avec Stack<T>
  7. Choisir la structure de données adaptée à un problème donné

Plan du chapitre

  1. Introduction aux collections génériques
  2. List<T> — Listes dynamiques
  3. Dictionary<TKey, TValue> — Dictionnaires
  4. HashSet<T> — Ensembles de valeurs uniques
  5. Queue<T> — Files d'attente (FIFO)
  6. Stack<T> — Piles (LIFO)
  7. Tableau comparatif et choix de structure
1

Introduction aux collections génériques

1. Introduction

1.1 Limites des tableaux classiques

Les tableaux vus au chapitre 1 disposent déjà de certaines méthodes utiles :

// Les tableaux offrent déjà tri et recherche int[] notes = { 15, 12, 18, 9 }; Array.Sort(notes); // Tri : {9, 12, 15, 18} int pos = Array.IndexOf(notes, 15); // Recherche : 2 int idx = Array.BinarySearch(notes, 12); // Recherche binaire : 1

Cependant, ils conservent des limites importantes :

  • Taille fixe : impossible d'ajouter ou supprimer des éléments après la création
  • Pas d'ajout/suppression dynamique : pas de Add(), Remove(), Insert()
  • Redimensionnement coûteux : il faut recréer un nouveau tableau et copier les données
// Problème : ajouter un élément à un tableau plein int[] tab = { 15, 12, 18 }; // tab[3] = 14; → IndexOutOfRangeException ! // Solution lourde : recréer un tableau plus grand int[] nouveau = new int[4]; Array.Copy(tab, nouveau, tab.Length); nouveau[3] = 14;

Les collections génériques résolvent ces limites en offrant des structures dynamiques, typées et riches en fonctionnalités.

1. Introduction

1.3 L'espace de noms System.Collections.Generic

Toutes les collections génériques se trouvent dans l'espace de noms :

using System.Collections.Generic;

Collections principales

CollectionDescriptionAnalogue
List<T>Liste dynamique indexéeTableau redimensionnable
Dictionary<K,V>Paires clé-valeurTable de hachage
HashSet<T>Ensemble de valeurs uniquesEnsemble mathématique
Queue<T>File d'attente (FIFO)File au guichet
Stack<T>Pile (LIFO)Pile d'assiettes
2

List<T> — Listes dynamiques

2. List<T>

2.1 Déclaration et initialisation

List<T> est une collection ordonnée, dynamique et indexée. Elle grandit automatiquement selon les besoins.
// Déclaration d'une liste vide List<int> nombres = new List<int>(); // Déclaration avec capacité initiale List<string> noms = new List<string>(10); // Déclaration avec initialisation List<int> notes = new List<int> { 15, 12, 18, 14 }; // Avec var (inférence de type) var villes = new List<string> { "Gafsa", "Tunis", "Sfax" };

2. List<T>

2.2 Méthodes principales

MéthodeDescription
Add(T)Ajoute un élément à la fin
Insert(index, T)Insère à une position donnée
Remove(T)Supprime la première occurrence
RemoveAt(index)Supprime à l'indice donné
Contains(T)Vérifie la présence d'un élément
IndexOf(T)Retourne l'indice (ou -1)
Sort()Trie la liste
Reverse()Inverse l'ordre
Clear()Vide la liste
CountNombre d'éléments (propriété)

2. List<T>

2.3 Exemples d'utilisation

List<string> etudiants = new List<string>(); // Ajout etudiants.Add("Ali"); etudiants.Add("Sara"); etudiants.Add("Omar"); etudiants.Insert(1, "Fatma"); // Insère à l'indice 1 // Accès par indice Console.WriteLine(etudiants[0]); // Ali // Suppression etudiants.Remove("Omar"); // Supprime "Omar" etudiants.RemoveAt(0); // Supprime "Ali" // Recherche bool existe = etudiants.Contains("Sara"); // true int pos = etudiants.IndexOf("Fatma"); // 0 // Parcours foreach (string e in etudiants) { Console.WriteLine(e); } Console.WriteLine($"Nombre : {etudiants.Count}");

2. List<T>

Exercice d'application

Exercice 2.1 — Gestion d'une liste de notes

Écrivez un programme qui :

  1. Crée une List<int> contenant les notes : 14, 8, 17, 11, 19, 6, 13
  2. Affiche le nombre total de notes
  3. Calcule et affiche la moyenne
  4. Supprime toutes les notes inférieures à 10 (éliminées)
  5. Trie les notes restantes par ordre croissant
  6. Affiche la liste finale

Exercice 2.2 — Traçage

Déterminez la sortie de ce code :

var lst = new List<int> { 5, 10, 15, 20 }; lst.Insert(2, 12); lst.RemoveAt(0); lst.Add(25); lst.Remove(20); Console.WriteLine(string.Join(", ", lst));

2. List<T>

Correction — Exercice 2.1

✓ Correction

List<int> notes = new List<int> { 14, 8, 17, 11, 19, 6, 13 }; // 1. Nombre total Console.WriteLine($"Nombre de notes : {notes.Count}"); // 7 // 2. Moyenne double somme = 0; foreach (int n in notes) somme += n; Console.WriteLine($"Moyenne : {somme / notes.Count:F2}"); // 12.57 // 3. Supprimer les notes < 10 notes.RemoveAll(n => n < 10); // 4. Trier notes.Sort(); // 5. Afficher Console.WriteLine(string.Join(", ", notes)); // 11, 13, 14, 17, 19

✓ Correction — Exercice 2.2 (Traçage)

Étapes : {5,10,15,20} → Insert(2,12): {5,10,12,15,20} → RemoveAt(0): {10,12,15,20} → Add(25): {10,12,15,20,25} → Remove(20): {10,12,15,25}

Sortie : 10, 12, 15, 25

3

Dictionary<TKey, TValue> — Dictionnaires

3. Dictionary<TKey, TValue>

3.1 Concept et déclaration

Un Dictionary<TKey, TValue> stocke des paires clé-valeur. Chaque clé est unique et permet un accès rapide à la valeur associée.
// Déclaration vide Dictionary<string, int> ages = new Dictionary<string, int>(); // Déclaration avec initialisation var capitales = new Dictionary<string, string> { { "Tunisie", "Tunis" }, { "France", "Paris" }, { "Algérie", "Alger" } }; // Syntaxe alternative (C# 6+) var scores = new Dictionary<string, int> { ["Ali"] = 85, ["Sara"] = 92, ["Omar"] = 78 };

3. Dictionary<TKey, TValue>

3.2 Méthodes et propriétés

Méthode / PropriétéDescription
Add(key, value)Ajoute une paire (exception si clé existe)
[key] = valueAjoute ou met à jour
Remove(key)Supprime la paire par clé
ContainsKey(key)Vérifie si la clé existe
ContainsValue(value)Vérifie si la valeur existe
TryGetValue(key, out val)Lecture sécurisée
CountNombre de paires
KeysCollection des clés
ValuesCollection des valeurs

3. Dictionary<TKey, TValue>

3.3 Exemples d'utilisation

var stock = new Dictionary<string, int>(); // Ajout stock.Add("Cahier", 50); stock["Stylo"] = 200; stock["Gomme"] = 75; // Accès Console.WriteLine(stock["Stylo"]); // 200 // Modification stock["Cahier"] = 45; // Met à jour la quantité // Lecture sécurisée if (stock.TryGetValue("Règle", out int qte)) Console.WriteLine(qte); else Console.WriteLine("Produit non trouvé"); // Parcours foreach (KeyValuePair<string, int> paire in stock) { Console.WriteLine($"{paire.Key} : {paire.Value}"); }

3. Dictionary<TKey, TValue>

Exercice d'application

Exercice 3.1 — Compteur de mots

Écrivez un programme qui :

  1. Prend un tableau de mots : { "pomme", "banane", "pomme", "orange", "banane", "pomme" }
  2. Utilise un Dictionary<string, int> pour compter les occurrences de chaque mot
  3. Affiche chaque mot avec son nombre d'occurrences

Exercice 3.2 — Traçage

Déterminez la sortie de ce code :

var d = new Dictionary<string, int> { {"a",1}, {"b",2}, {"c",3} }; d["b"] = 20; d.Remove("a"); d["d"] = 4; Console.WriteLine(d.Count); Console.WriteLine(d.ContainsKey("a")); Console.WriteLine(d["b"]);

3. Dictionary<TKey, TValue>

Correction — Exercice 3.1

✓ Correction

string[] mots = { "pomme", "banane", "pomme", "orange", "banane", "pomme" }; var compteur = new Dictionary<string, int>(); foreach (string mot in mots) { if (compteur.ContainsKey(mot)) compteur[mot]++; else compteur[mot] = 1; } foreach (var paire in compteur) { Console.WriteLine($"{paire.Key} : {paire.Value}"); } // pomme : 3 // banane : 2 // orange : 1

✓ Correction — Exercice 3.2 (Traçage)

Sortie : 3 puis False puis 20

Explication : après Remove("a") et ajout de "d", il reste 3 paires. La clé "a" n'existe plus. "b" vaut 20.

4

HashSet<T> — Valeurs uniques

4. HashSet<T>

4.1 Concept et déclaration

Un HashSet<T> est un ensemble de valeurs uniques et non ordonnées. Il ignore automatiquement les doublons et offre des recherches très rapides.
// Déclaration vide HashSet<int> nombres = new HashSet<int>(); // Déclaration avec initialisation var fruits = new HashSet<string> { "pomme", "banane", "orange" }; // Les doublons sont ignorés silencieusement fruits.Add("pomme"); // Retourne false, "pomme" existe déjà Console.WriteLine(fruits.Count); // Toujours 3

Caractéristiques

  • Pas d'accès par indice (pas de [0])
  • Pas d'ordre garanti
  • Recherche en O(1) : très performant
  • Idéal pour : éliminer les doublons, tester l'appartenance

4. HashSet<T>

4.2 Méthodes et opérations ensemblistes

MéthodeDescription
Add(T)Ajoute (retourne false si doublon)
Remove(T)Supprime un élément
Contains(T)Vérifie la présence
UnionWith(autre)Union : A ∪ B
IntersectWith(autre)Intersection : A ∩ B
ExceptWith(autre)Différence : A \ B
IsSubsetOf(autre)Vérifie si sous-ensemble
var A = new HashSet<int> { 1, 2, 3, 4 }; var B = new HashSet<int> { 3, 4, 5, 6 }; A.IntersectWith(B); // A contient maintenant : { 3, 4 }

4. HashSet<T>

4.3 Exemple : Éliminer les doublons

// Supprimer les doublons d'une liste List<int> avecDoublons = new List<int> { 1, 3, 5, 3, 1, 7, 5, 9 }; var sansDoublons = new HashSet<int>(avecDoublons); Console.WriteLine(string.Join(", ", sansDoublons)); // 1, 3, 5, 7, 9 // Reconvertir en liste si nécessaire List<int> listeUnique = new List<int>(sansDoublons); // Opérations ensemblistes var cours_Ali = new HashSet<string> { "C#", "Java", "SQL" }; var cours_Sara = new HashSet<string> { "Python", "C#", "SQL" }; // Cours en commun var communs = new HashSet<string>(cours_Ali); communs.IntersectWith(cours_Sara); // communs = { "C#", "SQL" }

4. HashSet<T>

Exercice d'application

Exercice 4.1 — Opérations ensemblistes

Soient deux groupes d'étudiants :

Groupe A : { "Ali", "Sara", "Omar", "Fatma" }

Groupe B : { "Sara", "Khaled", "Fatma", "Imen" }

Écrivez un programme qui affiche :

  1. Les étudiants présents dans les deux groupes (intersection)
  2. Tous les étudiants sans doublons (union)
  3. Les étudiants uniquement dans le groupe A (différence)

Exercice 4.2 — Traçage

var s = new HashSet<int> { 10, 20, 30 }; Console.WriteLine(s.Add(20)); Console.WriteLine(s.Add(40)); s.Remove(10); Console.WriteLine(s.Count); Console.WriteLine(s.Contains(10));

4. HashSet<T>

Correction — Exercice 4.1

✓ Correction

var grpA = new HashSet<string> { "Ali", "Sara", "Omar", "Fatma" }; var grpB = new HashSet<string> { "Sara", "Khaled", "Fatma", "Imen" }; // 1. Intersection var inter = new HashSet<string>(grpA); inter.IntersectWith(grpB); Console.WriteLine("Communs : " + string.Join(", ", inter)); // Sara, Fatma // 2. Union var union = new HashSet<string>(grpA); union.UnionWith(grpB); Console.WriteLine("Tous : " + string.Join(", ", union)); // Ali, Sara, Omar, Fatma, Khaled, Imen // 3. Différence (A \ B) var diff = new HashSet<string>(grpA); diff.ExceptWith(grpB); Console.WriteLine("Seulement A : " + string.Join(", ", diff)); // Ali, Omar

✓ Correction — Exercice 4.2 (Traçage)

Sortie : FalseTrue3False

Add(20) retourne false (doublon), Add(40) retourne true, après Remove(10) il reste {20,30,40}.

5

Queue<T> — File d'attente (FIFO)

5. Queue<T>

5.1 Concept et principe FIFO

Une Queue<T> est une file d'attente qui suit le principe FIFO (First In, First Out) : le premier élément ajouté est le premier retiré.

Enqueue (ajouter) → [ A | B | C | D ] → Dequeue (retirer)

Analogie : une file d'attente au guichet. Le premier arrivé est le premier servi.

MéthodeDescription
Enqueue(T)Ajoute à la fin de la file
Dequeue()Retire et retourne le premier élément
Peek()Consulte le premier sans le retirer
Contains(T)Vérifie la présence
CountNombre d'éléments

5. Queue<T>

5.2 Exemple : File d'attente d'impression

Queue<string> fileImpression = new Queue<string>(); // Ajout de documents à la file fileImpression.Enqueue("Rapport.pdf"); fileImpression.Enqueue("Facture.docx"); fileImpression.Enqueue("Photo.jpg"); Console.WriteLine($"En attente : {fileImpression.Count}"); // 3 // Consulter le prochain sans le retirer Console.WriteLine($"Prochain : {fileImpression.Peek()}"); // Rapport.pdf // Traitement de la file while (fileImpression.Count > 0) { string doc = fileImpression.Dequeue(); Console.WriteLine($"Impression de : {doc}"); } // Impression de : Rapport.pdf // Impression de : Facture.docx // Impression de : Photo.jpg Console.WriteLine($"File vide : {fileImpression.Count == 0}"); // True

5. Queue<T>

Exercice d'application

Exercice 5.1 — Simulation de guichet

Écrivez un programme qui simule un guichet de banque :

  1. Créez une Queue<string> avec 5 clients : "Client1" à "Client5"
  2. Servez (retirez) les 2 premiers clients en affichant leur nom
  3. Ajoutez 2 nouveaux clients : "Client6" et "Client7"
  4. Affichez le prochain client à servir (sans le retirer)
  5. Affichez le nombre de clients restants

Exercice 5.2 — Traçage

var q = new Queue<int>(); q.Enqueue(10); q.Enqueue(20); q.Enqueue(30); Console.WriteLine(q.Dequeue()); q.Enqueue(40); Console.WriteLine(q.Peek()); Console.WriteLine(q.Dequeue()); Console.WriteLine(q.Count);

5. Queue<T>

Correction

✓ Correction — Exercice 5.1

var guichet = new Queue<string>(); for (int i = 1; i <= 5; i++) guichet.Enqueue($"Client{i}"); // Servir les 2 premiers Console.WriteLine($"Servi : {guichet.Dequeue()}"); // Client1 Console.WriteLine($"Servi : {guichet.Dequeue()}"); // Client2 // Ajouter 2 nouveaux guichet.Enqueue("Client6"); guichet.Enqueue("Client7"); // Prochain à servir Console.WriteLine($"Prochain : {guichet.Peek()}"); // Client3 // Nombre restant Console.WriteLine($"En attente : {guichet.Count}"); // 5

✓ Correction — Exercice 5.2 (Traçage)

File: [10,20,30] → Dequeue()=10 → [20,30] → Enqueue(40) → [20,30,40] → Peek()=20 → Dequeue()=20 → [30,40] → Count=2

Sortie : 1020202

6

Stack<T> — Pile (LIFO)

6. Stack<T>

6.1 Concept et principe LIFO

Un Stack<T> est une pile qui suit le principe LIFO (Last In, First Out) : le dernier élément ajouté est le premier retiré.

Push (empiler) → | D |
             | C |
             | B |
             | A | → Pop retire D (le sommet)

Analogie : une pile d'assiettes. On prend toujours celle du dessus.

MéthodeDescription
Push(T)Empile un élément (au sommet)
Pop()Dépile et retourne le sommet
Peek()Consulte le sommet sans dépiler
Contains(T)Vérifie la présence
CountNombre d'éléments

6. Stack<T>

6.2 Exemple : Historique de navigation

Stack<string> historique = new Stack<string>(); // Navigation : visite de pages historique.Push("Accueil"); historique.Push("Produits"); historique.Push("Détail Produit"); historique.Push("Panier"); Console.WriteLine($"Page actuelle : {historique.Peek()}"); // Panier // Bouton "Retour" string pageQuittee = historique.Pop(); Console.WriteLine($"Retour depuis : {pageQuittee}"); // Panier Console.WriteLine($"Page actuelle : {historique.Peek()}"); // Détail Produit // Afficher tout l'historique Console.WriteLine("Historique :"); foreach (string page in historique) { Console.WriteLine($" {page}"); } // Détail Produit // Produits // Accueil

6. Stack<T>

6.3 Exemple : Inverser une chaîne

string mot = "GAFSA"; Stack<char> pile = new Stack<char>(); // Empiler chaque caractère foreach (char c in mot) { pile.Push(c); } // Dépiler pour obtenir l'inverse string inverse = ""; while (pile.Count > 0) { inverse += pile.Pop(); } Console.WriteLine(inverse); // ASFAG

Cas d'usage courants de Stack<T> : historique de navigation (bouton retour), fonctionnalité Annuler/Rétablir (Undo/Redo), évaluation d'expressions mathématiques, vérification de parenthèses.

6. Stack<T>

Exercice d'application

Exercice 6.1 — Vérification de parenthèses

Écrivez un programme qui utilise un Stack<char> pour vérifier si une chaîne contient des parenthèses correctement équilibrées.

Exemples :

  • "((a+b)*(c-d))"Équilibré
  • "(a+b))"Non équilibré
  • "((a+b)"Non équilibré

Algorithme : Parcourir chaque caractère. Si (, empiler. Si ), dépiler. À la fin, la pile doit être vide.

Exercice 6.2 — Traçage

var s = new Stack<int>(); s.Push(10); s.Push(20); s.Push(30); Console.WriteLine(s.Pop()); s.Push(40); s.Push(50); Console.WriteLine(s.Peek()); Console.WriteLine(s.Pop()); Console.WriteLine(s.Count);

6. Stack<T>

Correction — Exercice 6.1

✓ Correction

string expression = "((a+b)*(c-d))"; Stack<char> pile = new Stack<char>(); bool valide = true; foreach (char c in expression) { if (c == '(') { pile.Push(c); } else if (c == ')') { if (pile.Count == 0) { valide = false; break; } pile.Pop(); } } if (valide && pile.Count == 0) Console.WriteLine("Équilibré"); else Console.WriteLine("Non équilibré");

✓ Correction — Exercice 6.2 (Traçage)

Pile: [10,20,30] → Pop()=30 → [10,20] → Push(40,50) → [10,20,40,50] → Peek()=50 → Pop()=50 → [10,20,40] → Count=3

Sortie : 3050503

7

Tableau comparatif et choix de structure

7. Comparatif des collections

7.1 Quelle structure choisir ?

Collection Accès Ordre Doublons Cas d'usage
List<T> Par indice Maintenu Oui Liste ordonnée, accès aléatoire
Dictionary<K,V> Par clé Non garanti Clés uniques Associations clé→valeur
HashSet<T> Aucun Non garanti Non Valeurs uniques, opérations ensemblistes
Queue<T> Premier (FIFO) FIFO Oui File d'attente, traitement séquentiel
Stack<T> Dernier (LIFO) LIFO Oui Annuler, historique, récursion

7. Comparatif des collections

7.2 Guide de décision

Besoin d'une liste ordonnée avec accès par indice ?List<T>
Besoin d'associer des clés à des valeurs ?Dictionary<K,V>
Besoin de garantir l'unicité des éléments ?HashSet<T>
Traitement dans l'ordre d'arrivée (FIFO) ?Queue<T>
Traitement en ordre inverse (LIFO) ?Stack<T>

Complexité des opérations principales

OpérationListDictionaryHashSetQueueStack
AjoutO(1)*O(1)O(1)O(1)O(1)
RechercheO(n)O(1)O(1)O(n)O(n)
SuppressionO(n)O(1)O(1)O(1)O(1)
Accès indiceO(1)

* O(1) amorti, O(n) si redimensionnement nécessaire

7. Comparatif

Exercice de synthèse

Exercice 7.1 — Choix de structure

Pour chaque situation, indiquez la collection la plus adaptée et justifiez :

  1. Stocker les notes des étudiants avec accès par nom
  2. Gérer les pages visitées pour un bouton "Retour"
  3. Stocker la liste des inscrits à un cours (pas de doublons)
  4. Gérer une file de tickets de support technique
  5. Stocker une liste de courses modifiable

Exercice 7.2 — Programme complet

Écrivez un programme de gestion d'un restaurant :

  1. Utilisez un Dictionary<string, double> pour le menu (plat → prix)
  2. Utilisez une Queue<string> pour les commandes en attente
  3. Ajoutez 4 plats au menu et 3 commandes
  4. Traitez la première commande et affichez le prix du plat commandé

7. Comparatif

Correction — Exercice 7.1 et 7.2

✓ Correction — Exercice 7.1

  1. Dictionary<string, double> — accès par clé (nom)
  2. Stack<string> — LIFO pour le retour
  3. HashSet<string> — unicité garantie
  4. Queue<string> — FIFO, premier arrivé premier servi
  5. List<string> — ordonnée, modifiable, doublons possibles

✓ Correction — Exercice 7.2

var menu = new Dictionary<string, double> { ["Couscous"] = 15.50, ["Brik"] = 5.00, ["Lablabi"] = 4.50, ["Ojja"] = 8.00 }; var commandes = new Queue<string>(); commandes.Enqueue("Couscous"); commandes.Enqueue("Brik"); commandes.Enqueue("Ojja"); string plat = commandes.Dequeue(); Console.WriteLine($"Commande : {plat} — Prix : {menu[plat]} DT"); // Commande : Couscous — Prix : 15.5 DT

Résumé du chapitre

Concepts clés

  • Généricité et sécurité de type
  • List<T> : tableau dynamique
  • Dictionary<K,V> : paires clé-valeur
  • HashSet<T> : valeurs uniques

Principes

  • FIFO : Queue<T>
  • LIFO : Stack<T>
  • Choisir la bonne structure selon le besoin

Namespace

  • System.Collections.Generic

Prochain chapitre

  • Programmation orientée objet
  • Classes et objets
  • Encapsulation et propriétés
  • Constructeurs et destructeurs
Fin du Chapitre 2
Questions ?
Enseignant : Boubaker KHMILI
ISAE Gafsa — 2026/2027