Programmation 2 — 420-2A6-VI (Hiver 2026)
Notes de cours complètes — Prof. Tousignant Simon
Préalable : Prog 1 (420-1D6-VI)
Donne accès à : Prog 3, Jeux 2D, Jeux 3D, Applications mobiles, Nano-ordinateurs
Langage : C# | IDE : Visual Studio Community 2022 | Versioning : Git + GitHub Classroom
⚠️ Règles importantes du cours
- IA et générateurs de code strictement interdits (ChatGPT, Copilot, etc.) → exclusion immédiate de la période
- Cellulaires interdits dans le local (pauses incluses)
- 3 exclusions = démarches administratives avec l'API
- Paquets VS autorisés uniquement :
- Outils de développement d'application de bureau .NET
- Outils de développement .NET Framework 4.7.2
- C# et Visual Basic
- (Interdits : IntelliCode, IntelliSense avancé, LiveShare)
Convention Git
- 1 commit par fonctionnalité terminée et testée
- Nom de compte GitHub :
cgpvicto-[numéro_DA] - Plateforme : GitHub Classroom (groupes 00001 et 00002)
MODULE 1 — Types, tableaux et structures de contrôle
Types de base en C
Entiers
| Type | Octets | Min | Max |
|---|---|---|---|
short |
2 | -32 768 | 32 767 |
ushort |
2 | 0 | 65 535 |
int |
4 | -2 147 483 648 | 2 147 483 647 |
uint |
4 | 0 | 4 294 967 295 |
long |
8 | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
ulong |
8 | 0 | 18 446 744 073 709 551 615 |
Virgule flottante
| Type | Octets | Précision |
|---|---|---|
float |
4 | ~6-9 chiffres |
double |
8 | ~15-17 chiffres |
decimal |
16 | 28-29 chiffres |
Autres types courants
| Type | Octets | Notes |
|---|---|---|
bool |
1 | true / false |
char |
2 | 1 caractère Unicode |
string |
variable | Jusqu'à 2 147 483 647 caractères |
byte |
1 | 0 à 255 |
sbyte |
1 | -128 à 127 |
Tableaux
// Unidimensionnel
int[] nombres = { 1, 2, 3 };
// Multidimensionnel (matrice)
int[,] grille = new int[3, 3];
// Tableau de tableaux (jagged array)
int[][] tableau = new int[3][];
Structures de contrôle
// if / else if / else
if (condition) { } else if (condition2) { } else { }
// for
for (int i = 0; i < 10; i++) { }
// while
while (condition) { }
// do while (toujours exécuté au moins 1 fois)
do { } while (condition);
// foreach (lecture seule — ne modifie pas la structure)
foreach (int val in tableau) { }
Compilation et arguments de ligne de commande
Lors de la compilation, Visual Studio produit un exécutable dans [solution]/[projet]/bin/debug/.
public static void Main(string[] args)
{
foreach (string parametre in args)
{
Console.WriteLine(parametre);
}
}
Note : Les paramètres avec espaces doivent être entre guillemets dans l'invite de commande.
Pour simuler des arguments dans VS : Déboguer → Propriétés de déboguage → Arguments de la ligne de commande
Ce que le prof peut vous faire coder ici : tris (bulle, sélection, insertion), méthodes d'échange (Interchanger), validation des paramètres Main, application console avec paramètres typés.
MODULE 2 — Solution, projets et Git
Structure d'une solution Visual Studio
Une solution peut contenir plusieurs projets :
- Projet bibliothèque de classes (.dll) : contient les classes métier
- Projet application console : point d'entrée
- Projet de tests MSTest : tests automatisés
Pour importer un projet existant : File → Open → Project/Solution (puis pointer vers le .sln ou .csproj).
Git — Commandes essentielles
git init # Initialiser un dépôt
git add . # Ajouter tous les fichiers
git commit -m "Description" # Créer un commit
git status # État du dépôt
git log # Historique des commits
git push # Envoyer sur GitHub
git pull # Récupérer depuis GitHub
git clone [url] # Cloner un dépôt
Important : Toujours utiliser un fichier
.gitignorepour C# (ignorer les dossiersbin/,obj/,.vs/, etc.)
MODULE 3 — Déboguage et trace
Debug.WriteLine
Debug.WriteLine permet d'afficher des messages dans la fenêtre Output de Visual Studio sans modifier l'interface utilisateur.
using System.Diagnostics;
Debug.WriteLine("Valeur de x : " + x);
Debug.WriteLine($"Objet créé : {monObjet.Nom}");
- Les messages n'apparaissent qu'en mode Debug (pas en Release)
- Utile pour tracer l'exécution pas à pas sans
Console.WriteLine
MODULE 4 — Création de classes (POO)
Convention de nommage C
| Élément | Convention | Exemple |
|---|---|---|
| Champ privé | _camelCase avec underscore |
_nom, _dateNaissance |
| Propriété publique | PascalCase |
Nom, DateNaissance |
| Méthode | PascalCase |
CalculerAge() |
| Paramètre | camelCase |
nom, dateNaissance |
| Classe | PascalCase |
Etudiant, CompteBancaire |
| Interface | IPascalCase |
IEquatable, IComparable |
Structure d'une classe complète
/// <summary>
/// Représente un étudiant au cégep.
/// </summary>
public class Etudiant
{
// 1. Champs privés (backing fields)
private string _nom;
private int _numeroDa;
private string? _courriel; // nullable : peut être null
// 2. Propriétés avec validation dans le set
/// <summary>Obtient ou définit le nom de l'étudiant.</summary>
public string Nom
{
get => _nom;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Le nom ne peut pas être vide.");
_nom = value;
}
}
public int NumeroDa
{
get => _numeroDa;
set
{
// Validation par regex (ex: 7 chiffres)
if (!System.Text.RegularExpressions.Regex.IsMatch(value.ToString(), @"^\d{7}$"))
throw new ArgumentException("Le numéro de DA doit contenir 7 chiffres.");
_numeroDa = value;
}
}
// 3. Constructeur — utiliser les Propriétés (pas les champs _)
// pour forcer l'exécution des validations
/// <summary>
/// Initialise un nouvel étudiant.
/// </summary>
/// <param name="nom">Nom de l'étudiant.</param>
/// <param name="numeroDa">Numéro de DA (7 chiffres).</param>
/// <exception cref="ArgumentException">Si le nom ou le DA est invalide.</exception>
public Etudiant(string nom, int numeroDa)
{
Nom = nom; // passe par le set → validation exécutée
NumeroDa = numeroDa;
}
}
Points clés à retenir
- Toujours passer par la propriété dans le constructeur, pas par le champ
_directement - Documentation XML (
<summary>) obligatoire sur les membres publics — alimente l'IntelliSense - Balise
<exception>à ajouter manuellement sur les constructeurs et méthodes qui peuvent lancer - Pour renommer une classe proprement :
Ctrl+R, Ctrl+R(renommage global dans tout le projet) - Types nullables : ajouter
?après le type (ex:string?) pour accepternull
Ce que le prof peut vous faire coder ici : classe Etudiant, Produit, CompteBancaire, GardeDonjon, Animal, etc. avec champs privés, propriétés validées, constructeurs documentés.
MODULE 5 — Les exceptions
Principe fondamental
Une exception signale une situation anormale qui interrompt le flux normal d'exécution. Elle remonte dans la pile d'appels jusqu'à un catch, ou fait planter le programme.
// Lever une exception
throw new ArgumentException("Message clair pour le développeur.");
// Intercepter une exception
try
{
TraitementRisqué();
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
// Attraper tout le reste (frontière applicative)
Console.WriteLine("Erreur inattendue : " + ex.Message);
}
finally
{
// Toujours exécuté (libération de ressources)
}
Exceptions standards du framework .NET
| Exception | Quand l'utiliser |
|---|---|
ArgumentException |
Argument invalide (général) |
ArgumentNullException |
Argument null non autorisé |
ArgumentOutOfRangeException |
Valeur hors plage acceptable |
InvalidOperationException |
L'état de l'objet ne permet pas cette opération |
NotSupportedException |
Fonctionnalité non supportée |
FormatException |
Format de données incorrect |
Créer ses propres exceptions (exceptions métier)
public class SoldeInsuffisantException : Exception
{
public SoldeInsuffisantException(string message) : base(message) { }
}
// Usage :
if (solde < montant)
throw new SoldeInsuffisantException("Solde insuffisant pour ce retrait.");
Relancer une exception
catch (Exception ex)
{
// Log...
throw; // ✅ Conserve la pile d'appels originale
// throw ex; ❌ Réinitialise la pile d'appels (à éviter)
}
Règles d'or
- Ne pas mettre
try/catchpartout — seulement aux frontières applicatives (UI, API) - Bas niveau → on laisse remonter l'exception
- Frontière (UI/Vue) → on intercepte, on affiche, on log
- Jamais masquer une exception sans raison valable
NullReferenceException,IndexOutOfRangeException,DivideByZeroException→ souvent des bugs de conception, ne pas les cacher
Ce que le prof peut vous faire coder ici : classes avec validations qui lancent des exceptions, exceptions personnalisées (ex: DaInvalideException), try/catch dans un Main ou une interface.
MODULE 6 — Tests unitaires (MSTest)
Pourquoi tester ?
- Valider le comportement d'une méthode isolément
- Plus rapide et plus fiable que de tester manuellement via l'interface
- Permet de détecter les régressions lors de modifications
Le patron AAA (obligatoire)
[TestMethod]
public void NomMethode_Contexte_ResultatAttendu()
{
// ARRANGER — préparer les données
var moteur = new MoteurPromotion();
double prixInitial = 50;
// AGIR — exécuter la méthode à tester
double resultat = moteur.CalculerPrixFinal(prixInitial, 1, false);
// AFFIRMER — vérifier le résultat
Assert.AreEqual(prixInitial, resultat);
}
Les Assert disponibles
| Méthode | Utilité |
|---|---|
Assert.AreEqual(attendu, actuel) |
Vérifie l'égalité |
Assert.IsTrue(condition) |
Vérifie qu'une condition est vraie |
Assert.IsNull(objet) |
Vérifie que l'objet est null |
Assert.ThrowsException<T>(() => ...) |
Vérifie qu'une exception de type T est lancée |
CollectionAssert.AreEqual(c1, c2) |
Mêmes éléments dans le même ordre |
CollectionAssert.AreEquivalent(c1, c2) |
Mêmes éléments, ordre indifférent |
CollectionAssert.Contains(col, item) |
Vérifie la présence d'un élément |
StringAssert.Contains(str, sub) |
Vérifie qu'une chaîne contient une sous-chaîne |
Tests avec plusieurs jeux de données — [DataRow]
[TestMethod]
[DataRow(1200, 1, false, 1080.0, "Rabais : Prix > 1000")]
[DataRow(100, 15, false, 90.0, "Rabais : Quantité >= 10")]
[DataRow(100, 1, true, 90.0, "Rabais : Membre VIP")]
[DataRow(500, 2, false, 500.0, "Aucun rabais")]
public void CalculerPrix_Combinaisons_RetournePrixAttendu(
double prix, int qte, bool membre, double attendu, string scenario)
{
var moteur = new MoteurPromotion();
double actuel = moteur.CalculerPrixFinal(prix, qte, membre);
Assert.AreEqual(attendu, actuel, $"Échec : {scenario}");
}
Tester les exceptions
[TestMethod]
public void CalculerPrix_PrixNegatif_LanceArgumentException()
{
var moteur = new MoteurPromotion();
var ex = Assert.ThrowsException<ArgumentException>(() =>
moteur.CalculerPrixFinal(-10, 1, false)
);
StringAssert.Contains(ex.Message, "ne peut être négatif");
}
Ce que le prof peut vous faire coder ici : écrire des tests pour des classes avec validation (cas valide, cas invalide, cas limites), utiliser [DataRow] pour plusieurs scénarios, tester des listes avec CollectionAssert.
MODULE 7 — L'héritage
Concepts de base
L'héritage crée un lien "est un" entre une classe dérivée et une classe de base.
// Classe de base
public class Vehicule
{
private string _marque;
public string Marque { get => _marque; private set { /* validation */ _marque = value; } }
public Vehicule(string marque) { Marque = marque; }
// "virtual" = permission de redéfinir dans les sous-classes
public virtual void Klaxonner() => Console.WriteLine("Bip bip !");
}
// Classe dérivée — ": Vehicule" crée le lien "est un"
public class Camion : Vehicule
{
// "base(marque)" appelle le constructeur du parent
public Camion(string marque) : base(marque) { }
// "override" = on redéfinit le comportement
public override void Klaxonner() => Console.WriteLine("POUÊÊÊT !");
}
Résumé des mots-clés
| Mot-clé | Rôle |
|---|---|
: (après le nom de classe) |
Établit le lien "est un" |
virtual |
Donne la permission de redéfinir |
override |
Redéfinit le comportement hérité |
base(...) |
Appelle le constructeur/membre du parent |
protected |
Accessible dans la classe ET ses dérivées, mais pas à l'extérieur |
sealed (sur classe) |
Interdit de créer des sous-classes |
sealed (sur méthode) |
Interdit de redéfinir plus bas dans la hiérarchie |
Le polymorphisme
// L'étiquette est "Vehicule" mais la boîte peut être n'importe quelle sous-classe
Vehicule v = new Camion("Ford");
v.Klaxonner(); // Appelle la méthode de CAMION, pas de Vehicule → polymorphisme
// Impossible : l'étiquette "Vehicule" ne connaît pas NbEssieux (prop de Camion)
// Console.WriteLine(v.NbEssieux); ❌
// Pour accéder : caster explicitement
Camion c = (Camion)v;
Console.WriteLine(c.NbEssieux); // ✅
Collection polymorphe
List<Vehicule> garage = new List<Vehicule>();
garage.Add(new Voiture("Toyota", 4));
garage.Add(new Camion("Ford"));
garage.Add(new Moto("Honda"));
foreach (Vehicule v in garage)
{
v.Klaxonner(); // Chaque objet réagit selon sa propre implémentation
}
protected — accès hiérarchique
public class Vehicule
{
// get public (tout le monde peut lire)
// set protected (seules les classes dérivées peuvent modifier)
public bool EstEnMarche { get; protected set; }
}
public class Voiture : Vehicule
{
public void TomberEnPanne() { EstEnMarche = false; } // ✅ autorisé
}
// En dehors : maVoiture.EstEnMarche = false; ❌ erreur
Ce que le prof peut vous faire coder ici : hiérarchie Animal → Chien/Chat/Oiseau, Employe → Salarie/Contractuel, Vehicule → Voiture/Camion/Moto, liste polymorphe, redéfinition de méthodes.
MODULE 8 — Les interfaces
Principe
Une interface est un contrat : elle garantit qu'une classe implémente certaines méthodes/propriétés, sans imposer de lien hiérarchique.
public interface IImprimable
{
void Imprimer();
}
// Document ET Photo n'ont aucun lien, mais partagent le contrat
public class Document : IImprimable { public void Imprimer() { ... } }
public class Photo : IImprimable { public void Imprimer() { ... } }
IEquatable et IComparable — Les 4 cas obligatoires
Pour qu'une classe fonctionne correctement avec les tris et recherches .NET, il faut implémenter les 4 cas suivants :
public class Etudiant : IEquatable<Etudiant>, IComparable<Etudiant>, IComparable
{
private string _nom;
private double _moyenne;
// CAS 1 — IEquatable<T> : égalité basée sur les données
public bool Equals(Etudiant other)
{
if (other is null) return false;
return _nom == other._nom && _moyenne == other._moyenne;
}
// CAS 2 — Override de Equals(object) : relance le CAS 1
public override bool Equals(object obj)
{
Etudiant temp = obj as Etudiant;
if (temp is null) return false;
return Equals(temp);
}
// CAS 3 — IComparable<T> : ordre de tri
public int CompareTo(Etudiant other)
{
if (other is null) return 1; // on est "plus grand" que null
return _moyenne.CompareTo(other._moyenne);
}
// CAS 4 — IComparable (objet) : compatibilité, relance le CAS 3
public int CompareTo(object obj)
{
Etudiant temp = obj as Etudiant;
if (temp is null) return 1;
return CompareTo(temp);
}
// Obligatoire si Equals est redéfini
public override int GetHashCode() => HashCode.Combine(_nom, _moyenne);
}
Comparaison de chaînes → utiliser StringComparison.Ordinal
return string.Compare(_nom, other._nom, StringComparison.Ordinal);
// Rapide, prédictible, identique partout (comparaison binaire)
abstract, virtual, sealed, interface — Quand utiliser quoi ?
| Besoin | Solution |
|---|---|
| Classes liées par un concept commun + partage de code | Classe abstract |
| Comportement par défaut modifiable | Méthode virtual |
| Forcer une implémentation obligatoire dans les sous-classes | Méthode abstract |
| Verrouiller une classe/méthode (plus de dérivation) | sealed |
| Contrat entre classes sans lien hiérarchique | interface |
| Héritage multiple | interface (une classe ne peut hériter que d'une seule classe, mais implémenter plusieurs interfaces) |
// Classe abstraite — ne peut pas être instanciée directement
public abstract class Animal
{
public abstract void FaireDuBruit(); // OBLIGATION pour les sous-classes
public virtual void Dormir() => Console.WriteLine("Zzz..."); // comportement par défaut modifiable
}
public class Chien : Animal
{
public override void FaireDuBruit() => Console.WriteLine("Wouf !");
}
Ce que le prof peut vous faire coder ici : implémenter IEquatable/IComparable sur une classe, classe abstraite avec méthodes abstraites, comparaison et tri de listes d'objets.
MODULE 9 — Windows Forms (Interface graphique)
Création d'un projet WinForms
- Nouveau projet → Windows Forms App → .NET 10 → Langage C#
- Renommer
Form1.cs→FormPrincipal.cs(clic droit → Renommer → accepter le renommage des références)
Concepts clés
Partial Class — Un formulaire est divisé en deux fichiers :
- FormPrincipal.cs → votre code (événements, logique)
- FormPrincipal.Designer.cs → code généré automatiquement par le concepteur visuel (ne jamais modifier manuellement)
InitializeComponent() — Méthode générée dans .Designer.cs, appelée dans le constructeur. Initialise tous les contrôles visuels.
Convention de nommage des contrôles
Ne jamais laisser les noms par défaut (button1, textBox1). Renommer avec le type complet + description :
| Contrôle | Exemple de nom |
|---|---|
| Button | buttonValider, buttonEffacer |
| TextBox | textBoxNom, textBoxPrix |
| Label | labelNom, labelErreur |
| ComboBox | comboBoxClients |
| ListBox | listBoxProduits |
| DataGridView | dataGridViewFactures |
Propriétés importantes des contrôles
- Name : identifiant utilisé dans le code (convention ci-dessus)
- Text : texte affiché à l'écran (différent du Name)
- Location : position (X, Y) par rapport au formulaire (
Point) - Size : dimensions Width × Height (
Size) - TabIndex : ordre de navigation à la touche Tab (commence à 0)
Événements
// Double-cliquer sur un bouton dans le designer génère automatiquement :
private void buttonValider_Click(object sender, EventArgs e)
{
try
{
// Récupérer les valeurs de l'interface
string nom = textBoxNom.Text;
// Appeler le contrôleur (pas de logique métier dans la Vue !)
_controller.AjouterEtudiant(nom);
// Mettre à jour l'affichage
listBoxEtudiants.DataSource = null;
listBoxEtudiants.DataSource = _controller.ListeReadOnly;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Ce que le prof peut vous faire coder ici : formulaire avec boutons, zones de texte, listes déroulantes, gestion des événements Click, affichage dynamique d'une liste, MessageBox pour les erreurs.
MODULE 10 — Architecture MVC
L'analogie du restaurant
| Rôle MVC | Analogie | Dans le code |
|---|---|---|
| Modèle | Recette + ingrédients | Client.cs, Facture.cs — données brutes, validation |
| Contrôleur | Chef de cuisine | FactureController.cs — logique, manipulation des données |
| Vue | Serveur | Form.cs, Program.cs — interface, présentation, try/catch |
Structure physique (plusieurs projets dans la solution)
MaApplication.Lib ← Modèles + Contrôleurs (réutilisable)
MaApplication.Console ← Vue console (référence Lib)
MaApplication.WinForms ← Vue graphique (référence Lib)
MaApplication.Tests ← Tests unitaires (référence Lib)
Avantage : si on veut changer l'interface (Console → WinForms → Mobile), on garde Lib intact et on change seulement l'habillage.
Protection des données — IReadOnlyList
public class FactureController
{
private List<Facture> _historique = new List<Facture>(); // coffre-fort privé
// La Vue reçoit une "photo" — elle peut lire mais pas modifier directement
public IReadOnlyList<Facture> HistoriqueReadOnly => _historique.AsReadOnly();
}
Pourquoi ? Si on donnait la
Listdirecte à la Vue, elle pourrait la vider ou la modifier sans passer par le contrôleur.IReadOnlyList= lecture seule.
Workflow typique — Cycle d'une donnée
VUE : L'utilisateur clique "Ajouter"
↓
CONTRÔLEUR : reçoit l'action, vérifie que l'état est valide
↓ (si état invalide → throw InvalidOperationException)
MODÈLE : applique les règles métier, modifie les données
↓
CONTRÔLEUR : retourne le résultat à la Vue
↓
VUE : met à jour l'affichage
Le "Snapshot" dans les lignes de facture
// Quand on ajoute un item, on COPIE les valeurs au moment de l'achat
// (si le prix change demain, les anciennes factures restent correctes)
public class LigneFacture
{
public LigneFacture(Item item)
{
Description = item.Description; // copie
PrixUnitaire = item.Prix; // copie du prix ACTUEL
}
}
Méthodologie pour ajouter une fonctionnalité
- Modèle — les données sont-elles prêtes ?
- Contrôleur — ai-je une méthode pour cette action ?
- Test unitaire — ça marche sans ouvrir de fenêtre ?
- Vue — maintenant j'ajoute le bouton et le
try/catch
Gestion des erreurs dans la Vue
// La Vue intercepte TOUTES les exceptions et les affiche proprement
try
{
_factureCtrl.AjouterItem(itemSelectionne);
// Mettre à jour l'affichage...
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message); // Aucune facture ouverte, etc.
}
catch (Exception ex)
{
MessageBox.Show("Erreur inattendue : " + ex.Message);
}
Ce que le prof peut vous faire coder ici : projet MVC complet avec Lib + Vue (Console ou WinForms) + Tests, système de facturation, gestion d'étudiants ou de produits, séparation stricte des couches.
Résumé rapide — Mots-clés essentiels
| Mot-clé / Concept | Module | En une phrase |
|---|---|---|
private + _ |
4 | Champ interne caché |
get / set avec validation |
4 | Propriété avec garde-fou |
throw |
5 | Signaler une erreur |
try / catch |
5 | Attraper une erreur |
[TestMethod] |
6 | Déclarer un test |
Assert.AreEqual |
6 | Vérifier un résultat |
: (héritage) |
7 | Lien "est un" |
virtual / override |
7 | Permettre / redéfinir un comportement |
base() |
7 | Appeler le parent |
protected |
7 | Visible dans la hiérarchie seulement |
sealed |
7 | Verrouiller |
abstract |
8 | Modèle incomplet, obligation d'override |
interface |
8 | Contrat sans héritage |
IReadOnlyList |
10 | Donner accès en lecture seule |
| MVC | 10 | Séparer données / logique / interface |
Ressources
- Notes de cours : https://tousignantsimon-p2.github.io
- Visualisation des tris : https://visualgo.net/en/sorting
- Exceptions .NET : https://learn.microsoft.com/fr-fr/dotnet/standard/exceptions/
- Meilleures pratiques exceptions : https://learn.microsoft.com/fr-fr/dotnet/standard/exceptions/best-practices-for-exceptions
- Cheminement complet du programme : https://informatique.apical.xyz/