Quelques raccourcis :

420-KBB-LG – Programmation orientée objet avancée

Ceci est un petit site de support pour le cours 420-KBB-LG – Programmation orientée objet avancée.

Vous trouverez aussi des liens sur divers langages (dont C#, notre outil de prédilection dans ce cours) un peu partout dans http://h-deb.clg.qc.ca/. Portez une attention particulière à ../../../Sujets/Divers--cdiese/index.html.

Les diverses sections de cette page (en fonction desquelles vous trouverez quelques liens dans l'encadré à droite) vous mèneront elles-aussi sur des pistes qui vous permettront d'explorer un peu plus par vous-mêmes, de valider vos acquis et d'enrichir votre apprentissage.

Cliquez sur cette cible pour le plan de cours, sous forme électronique

Pratiques de correction

Je corrige les programmes en appliquant des codes de correction. Vous trouverez ici la liste des codes les plus fréquents.

Ma stratégie de correction en tant que telle (pour le code, à tout le moins) est résumée ici.

Cliquez sur cette cible pour les normes appliquées dans ce cours, en ce qui a trait au pseudocode

Quelques trucs pour demander de l'aide plus efficacement

Détail des séances en classe

Puisque nous serons en quelque sorte laboratoire à la fois pour les séances théoriques et les séances de laboratoire, j'ai fait le choix de construire le cours sous forme de 30 séances (de S00 à S29) plutôt que sous forme de 15 séances théoriques et 15 séances de laboratoire. Le dosage prévu de temps en théorie et de temps en laboratoire (soit environ moitié-moitié) devrait être respecté.

Date Séance Détails

23 août

S00

Au menu :

  • Présentation du cours et du plan de cours
  • Les outils dont nous aurons besoin :
    • Nous utiliserons C# 9.0
    • Nous ferons des projets .NET 5
    • Assurez-vous que votre version de Visual Studio soit à jour!
  • Échange sur le contenu du cours, les modalités, les attentes
    • Il est possible que nous tenions une séance « en ligne » pour « roder la mécanique » au cas où la pandémie mettrait du sable dans l'engrenage (personne ne le souhaite, mais mieux vaut être prudentes et prudents)
  • Réponses aux questions de la classe :
  • Présentation d'une petite activité formative : 420KBB--Consignes-activite-revision.pdf

Si vous souhaitez le code du programme principal à partir duquel vous devrez démarrer, vous pouvez le prendre du fichier PDF ou encore le prendre ci-dessous (parfois, copier / coller d'un PDF...) :

// ...
static void Main(string[] args)
{
   List<Orque> orques = new List<Orque>();
   try
   {
      for(string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
      {
         orques.Add(new Orque(s));
         Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
      }
   }
   catch(NomInvalideException ex)
   {
      Console.WriteLine(ex.Message);
   }
   if(Trier(ref orques, out int nbPermutations))
   {
      Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
   }
   else
   {
      Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
   }
   Console.Write("La tribu d'orques est :");
   foreach (Orque orque in orques)
      Console.Write($" {orque.Nom}");
}

À titre de référence, le code produit lors de cette séance pour démontrer le clonage était :

using System;
class Program
{
   enum Couleur { Rouge, Vert, Bleu }
   // il existe une interface ICloneable, qui expose une méthode Clone
   // ... mais ne l'utilisez pas :)
   abstract class Image
   {
      public Couleur Teinte { get; set; } 
      public abstract void Dessiner();
      protected Image(Couleur teinte)
      {
         Teinte = teinte;
      }
      protected Image(Image autre)
      {
         Teinte = autre.Teinte;
      }
      public abstract Image Cloner();
   }
   class Jpeg : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Jpeg de couleur {Teinte}");
      }
      public Jpeg(Couleur teinte) : base(teinte)
      {
      }
      // constructeur "de copie" (écart de langage)
      protected Jpeg(Jpeg autre) : base(autre)
      {
      }
      public override Image Cloner()
      {
         return new Jpeg(this);
      }
   }
   class Png : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Png de couleur {Teinte}");
      }
      public Png(Couleur teinte) : base(teinte)
      {
      }
      protected Png(Png autre) : base(autre)
      {
      }
      public override Image Cloner()
      {
         return new Png(this);
      }
   }
   class Bmp : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Bmp de couleur {Teinte}");
      }
      public Bmp(Couleur teinte) : base(teinte)
      {
      }
      protected Bmp(Bmp autre) : base(autre)
      {
      }
      public override Image Cloner()
      {
         return new Bmp(this);
      }
   }
   static Image ModifierPeutÊtre(Image img)
   {
      Image backup = img.Cloner(); // schéma de conception (Design Pattern :) )
//       Image backup = img; // 1) faire une copie de sauvegarde (backup) de img (oups!)
      Console.Write("Avant : ");
      img.Dessiner();
      img.Teinte = Couleur.Rouge; // 2) modifier img
      Console.Write("Après : ");
      img.Dessiner();
      // 3) demander à l'usager s'il veut conserver la modif
      Console.Write("Conserver la modification? (o/n) "); 
      char c = char.Parse(Console.ReadLine());
      if (char.ToLower(c) == 'o')
         return img; // 4) si oui, retourner l'entité modifiée
      return backup; // 5) sinon, retourner la copie de sauvegarde
   }
   static void Main()
   {
      Image img = new Jpeg(Couleur.Bleu);
      img = ModifierPeutÊtre(img);
      Console.Write("Résultat : ");
      img.Dessiner();
   }
}

26 août

S01

Note importante : il est probable que votre chix prof arrive avec un peu de retard ce matin (rentrée scolaire du plus jeune!), mais il arrivera éventuellement, alors profitez du moment d'attente pour faire progresser votre travail et vos réflexions.

Au menu :

  • Retour sur la petite activité formative proposée à S00
  • Discussion de divers aspects techniques et architecturaux associés à cette activité
  • Avenues de raffinement ou d'optimisation
  • Quelques explorations qui nous mèneront vers notre premier travail pratique, le TP00

À titre de référence, le code d'aujourd'hui est le suivant (ce code est perfectible; nous ferons bien mieux plus tard dans la session) :

using System;
using System.Collections.Generic;

class Program
{
   class Algos
   {
      //
      // Rappel : quand une fonction se limite à « return » suivi d'une
      // expression, les deux formes suivantes sont équivalentes (c'est
      // pour vous encourager à écrire de petites fonctions qui font une
      // et une seule chose!)
      //
      public static bool EstEntreInclusif(int val, int min, int max) =>
         min <= val && val <= max;
      //public static bool EstEntreInclusif(int val, int min, int max)
      //{
      //   return min <= val && val <= max;
      //}
   }
   static class OutilsTexte
   {
      private static List<char> voyelles = new List<char>(){ 'a', 'e', 'i', 'o', 'u', 'y' };
      public static bool EstVoyelle(char c) => voyelles.Contains(char.ToLower(c));
      public static int CompterVoyelles(string s)
      {
         int nb = 0;
         foreach (char c in s)
            if (EstVoyelle(c))
               ++nb;
         return nb;
      }
   }
   class NomInvalideException : Exception
   {
      public NomInvalideException(string nom) : base($"Nom invalide : {nom}")
      {
      }
   }
   class Orque
   {
      private static bool EstNomValide(string s)
      {
         const int MIN_CAR = 1,
                   MAX_CAR = 4;
         const int MAX_VOYELLES = 1;
         return s != null && Algos.EstEntreInclusif(s.Length, MIN_CAR, MAX_CAR) &&
                             OutilsTexte.CompterVoyelles(s) <= MAX_VOYELLES;
      }
      private string nom;
      public string Nom
      {
         get => nom;
         // private init
         // {
         //    if (!EstNomValide(value))
         //       throw new NomInvalideException(value);
         //    nom = value;
         // }
         private init
         {
            nom = !EstNomValide(value) ? throw new NomInvalideException(value) : value;
         }
      }
      public Orque(string nom)
      {
         Nom = nom;
      }
   }
   static void Permuter(ref Orque a, ref Orque b)
   {
      Orque temp = a;
      a = b;
      b = temp;
   }
   static bool Trier(ref List<Orque> lst, out int nbPerm)
   {
      nbPerm = 0;
      Orque[] tab = lst.ToArray();
      // tri à bulles... très, très naïf
      for (int i = 0; i < tab.Length - 1; ++i)
         for (int j = i + 1; j < tab.Length; ++j)
            //
            // s'ils ne sont pas dans l'ordre, les permuter
            //
            // deux versions du même test : méthode d'instance (en commentaire)
            // et méthode de classe (pas en commentaire). L'avantage de la méthode
            // de classe est qu'elle fonctionnera aussi dans le cas où l'un des deux
            // paramètres est null
            //
            //if (tab[i].Nom.CompareTo(tab[j].Nom) > 0)
            //
            if(string.Compare(tab[i].Nom, tab[j].Nom) > 0)
            {
               Permuter(ref tab[i], ref tab[j]);
               ++nbPerm;
            }
      lst = tab.ToList();
      return nbPerm == 0;
   }
   static void Main(string[] args)
   {
      List<Orque> orques = new List<Orque>();
      try
      {
         for (string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
         {
            orques.Add(new Orque(s));
            Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
         }
      }
      catch (NomInvalideException ex)
      {
         Console.WriteLine(ex.Message);
      }
      if (Trier(ref orques, out int nbPermutations))
      {
         Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
      }
      else
      {
         Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
      }
      Console.Write("La tribu d'orques est :");
      foreach (Orque orque in orques)
         Console.Write($" {orque.Nom}");
   }
}

En espérant que cela vous soit utile!

Nous avons aussi discuté sommairement du clonage. À titre de référence, le code produit lors de cette séance pour démontrer le clonage était :

using System;
class Program
{
   enum Couleur { Rouge, Vert, Bleu }
   // il existe une interface ICloneable, qui expose une méthode Clone
   // ... mais ne l'utilisez pas :)
   abstract class Image
   {
      public Couleur Teinte { get; private set; } 
      public void Modifier(Couleur teinte)
      {
         Teinte = teinte;
      }
      public abstract void Dessiner();
      protected Image(Couleur teinte)
      {
         Teinte = teinte;
      }
      protected Image(Image autre)
      {
         Teinte = autre.Teinte;
      }
      public abstract Image Cloner();
   }
   class Jpeg : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Jpeg de couleur {Teinte}");
      }
      public Jpeg(Couleur teinte) : base(teinte)
      {
      }
      // constructeur "de copie" (écart de langage)
      protected Jpeg(Jpeg autre) : base(autre)
      {
      }
      public override Image Cloner() => new Jpeg(this);
   }
   class Png : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Png de couleur {Teinte}");
      }
      public Png(Couleur teinte) : base(teinte)
      {
      }
      protected Png(Png autre) : base(autre)
      {
      }
      public override Image Cloner() => new Png(this);
   }
   class Bmp : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Bmp de couleur {Teinte}");
      }
      public Bmp(Couleur teinte) : base(teinte)
      {
      }
      protected Bmp(Bmp autre) : base(autre)
      {
      }
      public override Image Cloner() => new Bmp(this);
   }
   static Image ModifierPeutÊtre(Image img)
   {
      Image backup = img.Cloner(); // schéma de conception (Design Pattern :) )
//       Image backup = img; // 1) faire une copie de sauvegarde (backup) de img (oups!)
      Console.Write("Avant : ");
      img.Dessiner();
      img.Modifier(Couleur.Rouge); // 2) modifier img
      Console.Write("Après : ");
      img.Dessiner();
      // 3) demander à l'usager s'il veut conserver la modif
      Console.Write("Conserver la modification? (o/n) "); 
      char c = char.Parse(Console.ReadLine());
      // 4) si oui, retourner l'entité modifiée
      // 5) sinon, retourner la copie de sauvegarde
      return char.ToLower(c) == 'o'? img : backup;
   }
   static void Main()
   {
      Image img = new Jpeg(Couleur.Bleu);
      Console.Write("Avant : ");
      img.Dessiner();
      img = ModifierPeutÊtre(img);
      Console.Write("Après : ");
      img.Dessiner();
   }
}

30 août

S02

Au menu :

  • Propriétés : get, set et init
  • Créer une bibliothèque à liens dynamiques : création d'un petit système client / serveur
    • schéma de conception Interface et son implémentation en C#
    • implémentation(s) de cette interface
    • schéma de conception Fabrique
  • Écriture d'un client pour ce service
  • Utilité de ce type d'architecture
  • Présentation du TP00 (voir #tp pour les consignes de travaux pratiques en général)
  • Petit bonbon : introduction aux uplets
  • Travail sur le TP00

2 sept.

S03

Au menu :

Je vais revenir sur using, que je n'ai pas couvert aujourd'hui, et finally, le tout quand nous aurons rencontré des cas qui les rendent pertinent (ça s'en vient)

6 sept.

s/o

Pas de cours aujourd'hui (Fête du travail)

9 sept.

S04

Au menu :

  • Petite introduction à la complexité algorithmique et à la consommation de ressources :
    • pour un générateur séquentiel :
    • pour un générateur recycleur :
    • pour un générateur aléatoire naïf :
    • raffiner le générateur aléatoire
  • Examen du problème de la génération des statistiques :
    • conceptualiser les paires
    • introduction aux dictionnaires
    • traduction des paires clés / valeurs en uplets
  • Travail sur le TP00

N'oubliez pas de remettre votre TP00 avant  23 h 59 le 10 septembre

13 sept.

S05

Au menu :

À la demande générale, le code (incomplet mais fonctionnel) de l'observateur de clavier avec interfaces est :

using System;
using System.Collections.Generic;

namespace z
{
   
   class Program
   {
      //
      // Deux schémas de conception (Design Patterns)
      // ce matin : Singleton, Observateur
      //
      interface IRéactionClavier
      {
         void Réagir(ConsoleKeyInfo clé);
      }
      class Afficheur : IRéactionClavier
      {
         public void Réagir(ConsoleKeyInfo clé)
         {
            Console.Write(clé.KeyChar);
         }
      }
      interface ISignalFin
      {
         void Signaler();
         bool Terminé();
      }
      class CroqueMort : ISignalFin
      {
         bool Fin { get; set; } = false;
         public void Signaler() => Fin = true;
         public bool Terminé() => Fin;
      }
      class Terminateur : IRéactionClavier
      {
         ISignalFin Signaleur { get; init; }
         public Terminateur(ISignalFin p)
         {
            Signaleur = p;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == ConsoleKey.Q /*ConsoleKey.Escape*/)
               Signaleur.Signaler();
         }
      }
      class GesClavier
      {
         static GesClavier singleton = null;
         List<IRéactionClavier> Abonnés { get; } =
            new List<IRéactionClavier>();
         public void Abonner(IRéactionClavier p)
         {
            Abonnés.Add(p);
         }
         GesClavier()
         {
         }
         public static GesClavier GetInstance()
         {
            //
            // note : dangereux si multithreading
            //
            if (singleton == null)
               singleton = new GesClavier();
            return singleton;
         }
         public void Exécuter()
         {
            var clé = Console.ReadKey(true);
            foreach (var p in Abonnés)
               p.Réagir(clé);
         }
      }
      //
      // diantre, beaucoup de répétition de code n'est-ce
      // pas? mais on va dormir là-dessus pour le moment :)
      //
      class ÀDroite : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀDroite(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Est");
         }
      }
      class EnHaut : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnHaut(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Nord");
         }
      }
      class ÀGauche : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀGauche(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Ouest");
         }
      }
      class EnBas : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnBas(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Sud");
         }
      }
      static void Main()
      {
         var ges = GesClavier.GetInstance();
         var croc = new CroqueMort();
         ges.Abonner(new Terminateur(croc));
         ges.Abonner(new ÀDroite(ConsoleKey.D));
         ges.Abonner(new EnHaut(ConsoleKey.W));
         ges.Abonner(new ÀGauche(ConsoleKey.A));
         ges.Abonner(new EnBas(ConsoleKey.S));
         // ges.Abonner(new Afficheur());
         while (!croc.Terminé())
            ges.Exécuter();
      }
   }
}

À la demande générale, le code (incomplet mais fonctionnel) de l'observateur de clavier avec délégiés est :

using System;
using System.Collections.Generic;

namespace z
{
   
   class Program
   {
      // Deux schémas de conception (Design Patterns)
      // ce matin : Singleton, Observateur
      //interface IRéactionClavier
      //{
      //   void Réagir(ConsoleKeyInfo clé);
      //}
      delegate void RéactionClavier(ConsoleKeyInfo clé);
      class Afficheur // : IRéactionClavier
      {
         public void Afficher(ConsoleKeyInfo clé) // note: nom :)
         {
            Console.Write(clé.KeyChar);
         }
      }
      interface ISignalFin
      {
         void Signaler();
         bool Terminé();
      }
      class CroqueMort : ISignalFin
      {
         bool Fin { get; set; } = false;
         public void Signaler() => Fin = true;
         public bool Terminé() => Fin;
      }
      class Terminateur // : IRéactionClavier
      {
         ISignalFin Signaleur { get; init; }
         public Terminateur(ISignalFin p)
         {
            Signaleur = p;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == ConsoleKey.Q /*ConsoleKey.Escape*/)
               Signaleur.Signaler();
         }
      }
      class GesClavier
      {
         static GesClavier singleton = null;
         List<RéactionClavier> Abonnés { get; } =
            new List<RéactionClavier>();
         public void Abonner(RéactionClavier p)
         {
            Abonnés.Add(p);
         }
         GesClavier()
         {
         }
         public static GesClavier GetInstance()
         {
            // note : dangereux si multithreading
            if (singleton == null)
               singleton = new GesClavier();
            return singleton;
         }
         public void Exécuter()
         {
            var clé = Console.ReadKey(true);
            foreach (var réac in Abonnés)
               réac(clé);
         }
      }
      // diantre, beaucoup de répétition de code n'est-ce
      // pas? mais on va dormir là-dessus pour le moment :)
      class ÀDroite // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀDroite(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Est");
         }
      }
      class EnHaut // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnHaut(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Nord");
         }
      }
      class ÀGauche // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀGauche(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Ouest");
         }
      }
      class EnBas // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnBas(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Sud");
         }
      }
      static void Main()
      {
         var ges = GesClavier.GetInstance();
         var croc = new CroqueMort();
         // ges.Abonner(new Afficheur().Afficher);
         ges.Abonner(new Terminateur(croc).Réagir);
         ges.Abonner(new ÀDroite(ConsoleKey.D).Réagir);
         ges.Abonner(new EnHaut(ConsoleKey.W).Réagir);
         ges.Abonner(new ÀGauche(ConsoleKey.A).Réagir);
         ges.Abonner(new EnBas(ConsoleKey.S).Réagir);
         // ges.Abonner(new Afficheur());
         while (!croc.Terminé())
            ges.Exécuter();
      }
   }
}

16 sept.

S06

Au menu :

20 sept.

S07

Au menu :

  • Retour sur Q00
  • Retour sur le TP00
    • Mes pratiques de correction et mes codes de correction
    • Comment j'ai ajusté la pondération à votre avantage (pour MOD, NRC et NFP en particulier)
    • Sources de confusion :
      • Exceptions (où mettre un try? Type ou message?)
      • Qualifications d'accès (protected, public, internal...)
      • Sens du mot static
      • Quand afficher à la console... et quand ne pas le faire?
      • Rôle d'un constructeur (ou : que se passe-t-il quand un attribut d'instance – en particulier une string – n'est pas initialisé en C#)
  • Quelle police utiliser quand un imprime du code?
  • Travail sur le TP01

23 sept.

S08

Au menu :

  • Travail sur le TP01

Notez que je sais qu'il est parfois tentant de ne pas profiter du temps offert à titre de soutien en classe, mais ce travail est plus costaud que les précédents et je vous invite à profiter de l'occasion pour en assurer la saine progression... et pour poser des questions!

27 sept.

S09

Au menu :

  • Q01
  • Introduction à la programmation générique
    • Exemple de Afficher<T>, et pourquoi une version non-générique fonctionnerait tout autant dans ce cas
    • Exemple de TriBulles<T>, accompagné de Permuter<T>, avec une classe X qui est IComparable<X> en comparaison avec une classe Y qui ne l'est pas
    • Exemple de Pile<T> avec une capacité fixe
    • Exemple de Chercher<T> T doit être IEquatable<T>
  • Exercices de programmation à l'aide d'algorithmes génériques et d'expressions λ : exercice-apprivoiser-genericite-lambda.html

Les exemples de ce cours suivent. Pour la fonction Afficher générique :

using System;
using System.Collections.Generic;
class Program
{
   static void Afficher<T>(T n)
   {
      Console.Write($"{n} ");
   }
   static void Main()
   {
      Afficher(3);
      Afficher(3.14159f);
   }
} 
// ...

... pour la fonction TriBulles générique, de même que Permuter (j'ai ajouté la classe X sous sa forme IComparable<X> pour fins d'illustration; si vous la remplacez par une version qui n'est pas IComparable<X>, on ne pourra plus trier un X[]) :

using System;
using System.Collections.Generic;
class Program
{
   static void Permuter<T>(ref T a, ref T b)
   {
      T temp = a;
      a = b;
      b = temp;
   }

   static void TriBulles<T>(T [] tab) where T : IComparable<T>
   {
      for (int i = 0; i < tab.Length - 1; ++i)
         for (int j = i + 1; j < tab.Length; ++j)
            if (tab[i].CompareTo(tab[j]) > 0)
               Permuter(ref tab[i], ref tab[j]);
   }
   class X : IComparable<X>
   {
      public int CompareTo(X x) => 0;
   }
   class Y
   {
   }
   static void Main()
   {
      int[] tab = new[] { 3, 7, 11, 2, 5 };
      TriBulles(tab);
      foreach (int n in tab)
         Console.Write($"{n} ");
      Console.WriteLine();
      string[] strs = new[] { "man", "allo", "genre" };
      TriBulles(strs);
      foreach (var s in strs)
         Console.Write($"{s} ");
      TriBulles(new X[] { new X(), new X(), new X() });
      // TriBulles(new Y[] { new Y(), new Y(), new Y() });
   }
} 
// ...

... pour la classe Pile<T> :

using System;
using System.Collections.Generic;
class Program
{
   class Pile<T>
   {
      const int TAILLE_MAX = 10;
      T[] Éléments { get; } = new T[TAILLE_MAX];
      public int Count { get; private set; } = 0;
      public bool EstVide { get => Count == 0; }
      public bool EstPleine { get => Count == TAILLE_MAX; }
      public void Push(T élem)
      {
         if (EstPleine) throw new Exception(); // bof
         Éléments[Count] = élem;
         ++Count;
      }
      public void Pop()
      {
         if (EstVide) throw new Exception(); // bof
         --Count;
      }
      public T Top()
      {
         if (EstVide) throw new Exception(); // bof
         return Éléments[Count - 1];
      }
   }
   static void Main()
   {
      var pile = new Pile<string>();
      pile.Push("prof");
      pile.Push("mon");
      pile.Push("J'aime");
      while(!pile.EstVide)
      {
         Console.WriteLine(pile.Top());
         pile.Pop();
      }
   }
} 
// ...

... Pour la fonction Chercher<T> :

using System;
using System.Collections.Generic;
class Program
{
   static int Chercher<T>(T[] tab, T val) where T : IEquatable<T>
   {
      for (int i = 0; i != tab.Length; ++i)
         if (tab[i].Equals(val))
            return i;
      return -1;
   }
   static void Main()
   {
      string[] strs = new string[] { "Tomate", "Concombre", "Radis" };
      int n = Chercher(strs, "Radis");
      if (n == -1)
         Console.WriteLine("Pas trouvé...");
      else
         Console.WriteLine($"Trouvé à l'indice {n}");
   }
} 
// ...

30 sept.

S10

Au menu :

4 oct.

S11

Exceptionnellement, cette séance se tiendra en ligne (coordonnées envoyées par Colnet). Vous pouvez bien sûr la faire de notre labo au P-116 si vous le souhaitez. Je devrais être à l'heure, mais soyez patient(e)s si j'arrive un peu en retard (je dois passer un test de Covid et on ne contrôle pas la mécanique dans ces moments-là). À moins de nouvelles très vilaines (et très surprenantes, car ça ressemble vraiment à un vilain rhume), on se verra en personne à la séance S12.

Au menu :

  • Retour sur Q01
  • Brève discussion sur la date de remise du TP01
  • Retour sur les exercices sur la programmation générique (solutionnaire possible : exercice-apprivoiser-genericite-lambda--solutionnaire.html)
  • On se fait une petite collection simpliste :
    • On commence par une liste simplement chaînée de int
    • On la transforme ensuite en une petite collection générique, soit une liste simplement chaînée de T
    • On ajuste cette collection pour qu'il soit possible de la traverser avec foreach
    • Je me suis permis de mentionner le mot clé default pour initialiser un objet avec sa valeur par défaut (0 pour un int, 0.0 pour un double, null pour une string, etc.)
  • Travail sur le TP01

Le code de notre Liste<T> (liste simplement chaînée générique) était comme suit (j'ai ajouté une levée d'exception dans le cas où on essaierait de supprimer un élément d'une liste vide; en classe, je m'étais limité à un commentaire) :

using System;
using System.Collections;
using System.Collections.Generic;
class ListeVideException : Exception {}
class Program
{
   class Liste<T> : IEnumerable<T>
   {
      class Énumérateur : IEnumerator<T>
      {
         Noeud Cur { get; set; }
         public bool MoveNext()
         {
            if (Cur.Succ == null) return false;
            Cur = Cur.Succ;
            return true;
         }

         public void Reset() => throw new NotImplementedException();
         public void Dispose() { }

         public T Current => Cur.Valeur;
         object IEnumerator.Current => Cur.Valeur;

         public Énumérateur(Noeud tête)
         {
            Cur = new Noeud(); // sorte de "pré-tête"
            Cur.Succ = tête;
         }
      }
      // IEnumerator<T> :
      // MoveNext() ==> essaie d'avancer au prochain élément, retourne true si ça a fonctionné
      // Current ==> valeur pointée par l'énumérateur

      // foreach(var e in lst)
      //    f(e)

      // Intervalle à demi-ouvert : début exclus, fin incluse

      // for(IEnumerator<T> e = lst.GetEnumerator(); e.MoveNext(); )
      //    f(e.Current);
      //
      public IEnumerator<T> GetEnumerator() => new Énumérateur(Tête);
      class Noeud
      {
         public T Valeur { get; init; } = default;
         public Noeud Succ { get; set; } = null;
         public Noeud()
         {
         }
         public Noeud(T val)
         {
            Valeur = val;
         }
      }
      Noeud Tête { get; set; } = null;
      Noeud Queue { get; set; } = null;
      public bool EstVide => Count == 0;
      public int Count { get; private set; } = 0;
      public void AjouterDébut(T val)
      {
         var p = new Noeud(val);
         p.Succ = Tête;
         Tête = p;
         if (EstVide) Queue = p;
         ++Count;
      }
      //Noeud Trouver(T val) // en haut : Liste<T> where T : IEquatable<T>
      //{
      //   for (var p = Tête; p != null; p = p.Succ)
      //      if (p.Valeur.Equals(val))
      //         return p;
      //   return null;
      //}
      //void InsérerAprès(Noeud p, T val)
      //{
      //   var q = new Noeud(val);
      //   q.Succ = p.Succ;
      //   p.Succ = q;
      //}
      public void AjouterFin(T val)
      {
         if (EstVide)
            AjouterDébut(val);
         else
         {
            var p = new Noeud(val);
            Queue.Succ = p;
            Queue = p;
            ++Count;
         }
      }
      public void SupprimerDébut()
      {
         if(EstVide) throw new ListeVideException();
         Tête = Tête.Succ;
         --Count;
         if (EstVide) Queue = null;
      }

      IEnumerator IEnumerable.GetEnumerator() => new Énumérateur(Tête);
      //public void SupprimerFin()
      //{
      //   // piarkkk....
      //}
      //public void Afficher()
      //{
      //   for (var p = Tête; p != null; p = p.Succ)
      //      Console.WriteLine(p.Valeur);
      //}
   }
   public static void Main()
   {
      Liste<int> lst = new();
      for (int i = 0; i != 10; ++i)
         lst.AjouterFin(i + 1);
      foreach (var e in lst)
         Console.WriteLine(e);
   }
}

7 oct.

S12

Au menu :

  • Petit bonbon à propos de l'interface IEnumerable<T>
  • Travail sur le TP01

Il y aura un minitest Q02 à la séance S13, au retour de la semaine de récupération, alors prenez soin de ne pas trop décrocher entre-temps!

11 oct.

s/o

Pas de cours aujourd'hui (Action de grâce)

14 oct.

s/o

Pas de cours aujourd'hui (journée de mise à niveau)

18 oct.

S13

Au menu :

Petit exemple inspiré de celui donné en classe :

   static (T,long) Test<T>(Func<T> f)
   {
      var sw = new System.Diagnostics.Stopwatch();
      sw.Start();
      T res = f();
      sw.Stop();
      return (res, sw.ElapsedTicks);
   }
   static void Main()
   {
      const int N = 1_000_000;
      var (r0,dt0) = Test(() =>
      {
         int n = 0;
         var th0 = new Thread(() =>
         {
            for (int i = 0; i != N; ++i)
               ++n;
         });
         var th1 = new Thread(() =>
         {
            for (int i = 0; i != N; ++i)
               ++n;
         });
         th0.Start();
         th1.Start();
         th1.Join();
         th0.Join();
         return n;
      });
      var (r1, dt1) = Test(() =>
      {
         int n = 0;
         var mutex = new object();
         var th0 = new Thread(() =>
         {
            for (int i = 0; i != N; ++i)
               lock (mutex)
               {
                  ++n;
               }
         });
         var th1 = new Thread(() =>
         {
            for (int i = 0; i != N; ++i)
               lock (mutex)
               {
                  ++n;
               }
         });
         th0.Start();
         th1.Start();
         th1.Join();
         th0.Join();
         return n;
      });
      var (r2, dt2) = Test(() =>
      {
         int n = 0;
         var mutex = new object();
         var th0 = new Thread(() =>
         {
            int m = 0;
            for (int i = 0; i != N; ++i)
               ++m;
            lock (mutex)
            {
               n += m;
            }
         });
         var th1 = new Thread(() =>
         {
            int m = 0;
            for (int i = 0; i != N; ++i)
               ++m;
            lock (mutex)
            {
               n += m;
            }
         });
         th0.Start();
         th1.Start();
         th1.Join();
         th0.Join();
         return n;
      });
      Console.WriteLine($"Sans synchro : {r0} obtenu en {dt0} tics");
      Console.WriteLine($"Avec synchro : {r1} obtenu en {dt1} tics");
      Console.WriteLine($"Avec synchro : {r2} obtenu en {dt2} tics");
   }
}

Petit exemple de code qui devrait être rapide mais ne l'est pas... même s'il donne la bonne réponse!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
class Program
{
   static short[] CréerTableau(int n)
   {
      short[] tab = new short[n];
      for (int i = 0; i != tab.Length; ++i)
         tab[i] = (short)(i * 2 + 1);
      return tab;
   }
   static int CompterSi<T>(T[] tab, Func<T, bool> pred, int début, int fin) // début inclus, fin exclue
   {
      int n = 0;
      for (int i = début; i != fin; ++i)
         if (pred(tab[i]))
            ++n;
      return n;
   }
   static (T rés, long dt) Tester<T>(Func<T> f)
   {
      var sw = new Stopwatch();
      sw.Start();
      T rés = f();
      sw.Stop();
      return (rés, sw.ElapsedMilliseconds);
   }
   static int CompterMT(int nbThreads, short [] tab)
   {
      int[] nbImpairs = new int[nbThreads]; // initialisé à 0 en C#
      int tailleBloc = tab.Length / nbThreads;
      Thread[] thrs = new Thread[nbThreads - 1];
      for(int i = 0; i != thrs.Length; ++i)
      {
         int index = i;
         int début = index * tailleBloc;
         int fin = début + tailleBloc;
         thrs[index] = new Thread(() =>
         {
            for (int j = début; j != fin; ++j)
               if (tab[j] % 2 != 0)
                  ++nbImpairs[index];
         });
      }
      foreach (var th in thrs) th.Start();
      ////
      {
         int début = (nbThreads - 1) * tailleBloc;
         int fin = tab.Length;
         for (int j = début; j != fin; ++j)
            if (tab[j] % 2 != 0)
               ++nbImpairs[nbThreads - 1];
      }
      ////
      foreach (var th in thrs) th.Join();
      int somme = 0;
      foreach (int n in nbImpairs)
         somme += n;
      return somme;
   }
   static void Main()
   {
      const int N = 25_000;
      var tab = CréerTableau(N * N);

      var (r0, dt0) = Tester(() => CompterMT(1, tab));
      Console.WriteLine($"1 fil  : compté {r0} impairs en {dt0} ms");
      var (r1, dt1) = Tester(() => CompterMT(2, tab));
      Console.WriteLine($"2 fils : compté {r1} impairs en {dt1} ms");
      var (r2, dt2) = Tester(() => CompterMT(4, tab));
      Console.WriteLine($"4 fils : compté {r2} impairs en {dt2} ms");
      var (r3, dt3) = Tester(() => CompterMT(8, tab));
      Console.WriteLine($"8 fils : compté {r3} impairs en {dt3} ms");
   }
}

Petit exemple de code qui devrait être rapide et l'est... avec un tout petit changement!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
class Program
{
   static short[] CréerTableau(int n)
   {
      short[] tab = new short[n];
      for (int i = 0; i != tab.Length; ++i)
         tab[i] = (short)(i * 2 + 1);
      return tab;
   }
   static int CompterSi<T>(T[] tab, Func<T, bool> pred, int début, int fin) // début inclus, fin exclue
   {
      int n = 0;
      for (int i = début; i != fin; ++i)
         if (pred(tab[i]))
            ++n;
      return n;
   }
   static (T rés, long dt) Tester<T>(Func<T> f)
   {
      var sw = new Stopwatch();
      sw.Start();
      T rés = f();
      sw.Stop();
      return (rés, sw.ElapsedMilliseconds);
   }
   static int CompterMT(int nbThreads, short [] tab)
   {
      int[] nbImpairs = new int[nbThreads]; // initialisé à 0 en C#
      int tailleBloc = tab.Length / nbThreads;
      Thread[] thrs = new Thread[nbThreads - 1];
      for(int i = 0; i != thrs.Length; ++i)
      {
         int index = i;
         int début = index * tailleBloc;
         int fin = début + tailleBloc;
         thrs[index] = new Thread(() =>
         {
            int nb = 0;
            for (int j = début; j != fin; ++j)
               if (tab[j] % 2 != 0)
                  nb++;
            nbImpairs[index] = nb;
         });
      }
      foreach (var th in thrs) th.Start();
      ////
      {
         int début = (nbThreads - 1) * tailleBloc;
         int fin = tab.Length;
         int nb = 0;
         for (int j = début; j != fin; ++j)
            if (tab[j] % 2 != 0)
               ++nb;
         nbImpairs[nbThreads - 1] = nb;
      }
      ////
      foreach (var th in thrs) th.Join();
      int somme = 0;
      foreach (int n in nbImpairs)
         somme += n;
      return somme;
   }
   static void Main()
   {
      const int N = 25_000;
      var tab = CréerTableau(N * N);

      for(int i = 1; i <= 16; ++i)
      {
         var (r, dt) = Tester(() => CompterMT(i, tab));
         Console.WriteLine($"{i} fil(s)  : compté {r} impairs en {dt} ms");
      }
   }
}

21 oct.

S14

Au menu :

L'exemple utilisé en classe pour illustrer le faux-partage ressemblait à :

using System;
using System.Threading;
using System.Diagnostics;

class Program
{
   static short[] CréerTableau(int n)
   {
      short[] tab = new short[n];
      for (int i = 0; i != tab.Length; ++i)
         tab[i] = (short)(i * 2 + 1);
      return tab;
   }
   static (T rés, long dt) Tester<T>(Func<T> f)
   {
      var sw = new Stopwatch();
      sw.Start();
      T rés = f();
      sw.Stop();
      return (rés, sw.ElapsedMilliseconds);
   }

   static int CompterSiMT(short[] tab, Func<short, bool> pred, int nthrs)
   {
      var thrs = new Thread[nthrs-1]; // initialisés à null en C#
      var nimpairs = new int[nthrs]; // initialisés à 0 en C#
      int tailleBloc = tab.Length / nthrs;

      for(int i = 0; i < thrs.Length; ++i)
      {
         int monIndice = i;
         int début = i * tailleBloc; // inclus
         int fin = (i + 1) * tailleBloc; // exclue
         thrs[i] = new Thread(() =>
         {
            int m = 0;
            for (; début != fin; ++début)
               if (pred(tab[début]))
                  ++m;
            nimpairs[monIndice] = m;
            //for (; début != fin; ++début)
            //   if (pred(tab[début]))
            //      ++nimpairs[monIndice];
         });
      }
      foreach (var th in thrs)
         th.Start();
      {
         int début = (nthrs - 1) * tailleBloc; // inclus
         int fin = tab.Length; // exclue
         int m = 0;
         for (; début != fin; ++début)
            if (pred(tab[début]))
               ++m;
         nimpairs[nthrs - 1] = m;
         //for (; début != fin; ++début)
         //   if (pred(tab[début]))
         //      ++nimpairs[nthrs - 1];
      }
      foreach (var th in thrs)
         th.Join();
      int cumul = 0;
      foreach (int n in nimpairs)
         cumul += n;
      return cumul;
   }

   static void Main()
   {
      const int N = 25_000;
      var tab = CréerTableau(N * N);
      for(int i = 1; i <= 16; ++i)
      {
         var (r0, dt0) = Tester(() => CompterSiMT(tab, n => n % 2 != 0, i));
         if(i < 10) // bof, mais je ne me souviens plus du code de formatage...
            Console.WriteLine($"Compté {r0} impairs avec  {i} fils en {dt0} ms");
         else
            Console.WriteLine($"Compté {r0} impairs avec {i} fils en {dt0} ms");
      }
   }
}

Le code du pipeline que nous avons implémenté est :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.IO;
using System.Diagnostics;
class Program
{
   static void Permuter<T>(ref T a, ref T b)
   {
      T temp = a;
      a = b;
      b = temp;
   }
   class ZoneTransit<T>
   {
      object mutex = new object();
      List<T> data = new();
      public void Ajouter(T elem) // à rediscuter (API suspecte)
      {
         lock (mutex)
            data.Add(elem);
      }
      public List<T> Extraire()
      {
         List<T> lst = new();
         lock (mutex)
            Permuter(ref lst, ref data);
         return lst;
      }
   }
   static string LireFichier(string nom)
   {
      using (var fich = new StreamReader(nom))
         return fich.ReadToEnd();
   }
   static (string, string) Majusculer(string s, string nom)
      => (s.ToUpper(), nom);
   static (string,string) Censurer(string s, string nom)
   {
      string résultat = "";
      int début = 0;
      string àCensurer = "IF";
      int pos = s.IndexOf(àCensurer, début);
      while(pos != -1)
      {
         résultat += s.Substring(début, pos - début);
         résultat += "[CENSURÉ]";
         début = pos + àCensurer.Length;
         pos = s.IndexOf(àCensurer, début);
      }
      résultat += s.Substring(début);
      return (résultat, nom);
   }
   static void Écrire(string s, string nom)
   {
      using (var sw = new StreamWriter(nom + ".out"))
         sw.Write(s);
   }

   static void Main(string [] args)
   {
      var fini = new bool[3];
      var zt = new ZoneTransit<(string, string)>[3]
      {
         new(), new(), new()
      };
     
      var thrs = new Thread[]
      {
         // lecteur
         new Thread(() =>
         {
            foreach(var nom in args)
               zt[0].Ajouter((LireFichier(nom), nom));
            fini[0] = true; // dès maintenant, je ne produis plus
         }),
         // majusculeur
         new Thread(() =>
         {
            while(!fini[0])
            {
               var lst = zt[0].Extraire();
               foreach(var (s, nom) in lst)
                  zt[1].Ajouter(Majusculer(s, nom));
            }
            {
               var lst = zt[0].Extraire();
               foreach(var (s, nom) in lst)
                  zt[1].Ajouter(Majusculer(s, nom));
            }
            fini[1] = true; // dès maintenant, je ne produis plus
         }),
         // censeur
         new Thread(() =>
         {
            while(!fini[1])
            {
               var lst = zt[1].Extraire();
               foreach(var (s, nom) in lst)
                  zt[2].Ajouter(Censurer(s, nom));
            }
            {
               var lst = zt[1].Extraire();
               foreach(var (s, nom) in lst)
                  zt[2].Ajouter(Censurer(s, nom));
            }
            fini[2] = true;
         }),
         // scripteur
         new Thread(() =>
         {
            while(!fini[2])
            {
               var lst = zt[2].Extraire();
               foreach(var (s, nom) in lst)
                  Écrire(s, nom);
            }
            {
               var lst = zt[2].Extraire();
               foreach(var (s, nom) in lst)
                  Écrire(s, nom);
            }
         })
      };
      foreach (var th in thrs) th.Start();
      foreach (var th in thrs) th.Join();
   }
}

25 oct.

S15

Je serai absent cette semaine car je donne des conférences à CppCon. Vous pourrez suivre mes aventures sur : ../../../Sujets/Orthogonal/cppcon2021.html

28 oct.

S16

Je serai absent cette semaine car je donne des conférences à CppCon. Vous pourrez suivre mes aventures sur : ../../../Sujets/Orthogonal/cppcon2021.html

1 nov.

S17

Activité préparatoire au TP02, que je devrais vous passer plus tard cette semaine. Soit le code client suivant :

using System;
using System.Threading;

namespace ActivitéArdoise
{
   class Program
   {
      class Caméra : IObservateurArdoise
      {
         public void Nouveauté(string qui, string quoi)
         {
            Console.WriteLine($"{qui} a dit {quoi}");
         }
      }
      static void Main(string[] args)
      {
         var bb = new Ardoise();
         bb.Abonner(new Caméra());
         var noms = new[] { "Bill", "Bob" };
         var pub = new int[noms.Length];
         for(int i = 0; i != noms.Length; ++i)
            pub[i] = bb.AjouterPublieur(noms[i]);
         bool fini = false;
         var th = new Thread[pub.Length];
         for(int i = 0; i != th.Length; ++i)
         {
            int indice = i;
            th[i] = new Thread(() =>
            {
               var dé = new Random();
               for (int n = 0; !fini;)
               {
                  if (dé.Next() % pub.Length == indice)
                     bb.Publier(pub[indice], $"{noms[indice]} dit : \"ceci est mon message {n++}\"");
                  Thread.Sleep(1000);
               }
            });
         }
         foreach (var thr in th) thr.Start();
         Console.ReadKey(true);
         fini = true;
         foreach (var thr in th) thr.Join();
      }
   }
}

Votre objectif est d'offrir une classe Ardoise qui exposera au minimum les services suivants :

  • Abonner(IObservateurArdoise) pour accepter un nouvel observateur
  • AjouterPublieur(string nom) qui ajoute un publieur nommé nom et retourne le id (un int) qui lui sera associé (levez une exception s'il existe déjà un publieur de ce nom). C'est à l'Ardoise de trouver une stratégie pour donner un id différent à chaque publieur
  • Publier(int id, string message) qui publiera un message sur l'Ardoise
  • RetirerPublieur(int id) qui retire un publieur ayant l'identifiant id de la liste des publieurs autorisés de l'Ardoise (levez une exception s'il n'existe pas de publieur avec cet identifiant)
  • Quand une publication sera faite sur l'Ardoise, elle doit informer ses abonnés (de type IObservateurArdoise) en appelant leur méthode Nouveauté(string qui, string quoi)

Dans le TP02, il y aura une Ardoise différente mais selon le même modèle, alors vous ne perdez pas votre temps ce matin

Pour voir si ça tient la route, assurez-vous de tester votre code. Des exemples possibles de tests (rien d'exhaustif) :

      static void StressTestA(int n)
      {
         var bb = new Ardoise();
         var noms = new[] { "Bill", "Bob" };
         var pub = new int[noms.Length];
         var th = new Thread[pub.Length];
         for(int i = 0; i != th.Length; ++i)
         {
            int indice = i;
            th[i] = new Thread(() =>
            {
               for(int j = 0; j != n; ++j)
               {
                  int id = bb.AjouterPublieur(noms[indice]);
                  bb.Publier(id, "Coucou #{j}");
                  bb.RetirerPublieur(id);
               }
            });
         }
         foreach (var thr in th) thr.Start();
         foreach (var thr in th) thr.Join();
      }
      class Compteur : IObservateurArdoise
      {
         Dictionary<string, int> Compte { get; } = new();
         public void Nouveauté(string qui, string quoi)
         {
            lock(this)
            {
               if (Compte.ContainsKey(qui))
                  Compte[qui]++;
               else
                  Compte.Add(qui, 1);
            }
         }
         public void Rapport()
         {
            foreach (var (k, v) in Compte)
               Console.WriteLine($"{k} a publié {v} fois");
         }
      }
      static void StressTestB(int n)
      {
         Compteur compteur = new();
         var bb = new Ardoise();
         bb.Abonner(compteur);
         var noms = new[] { "Bill", "Bob" };
         var pub = new int[noms.Length];
         var th = new Thread[pub.Length];
         for (int i = 0; i != th.Length; ++i)
         {
            int indice = i;
            th[i] = new Thread(() =>
            {
               for (int j = 0; j != n; ++j)
               {
                  int id = bb.AjouterPublieur(noms[indice]);
                  bb.Publier(id, "Coucou #{j}");
                  bb.RetirerPublieur(id);
               }
            });
         }
         foreach (var thr in th) thr.Start();
         foreach (var thr in th) thr.Join();
         compteur.Rapport();
      }

4 nov.

S18

Au menu :

  • Q03
  • Activité préparatoire au TP02 :
    • Créez un type PanneauAffichage, qui aura entre autres comme états une position (coin en haut et à gauche) de même qu'une hauteur et une largeur
    • Il sera possible d'écrire (méthode Write(string, ConsoleColor)) sur un PanneauAffichage. Écrire sur un panneau n'écrira pas à l'écran (truc : gardez dans un PanneauAffichage un tableau 2D de paires (char,ConsoleColor)). Si une string contient des sauts de ligne ('\n'), alors elle sera représentée sur plusieurs lignes. Écrire dans un PanneauAffichage remplace la totalité du texte colorié qui s'y trouvait précédemment
    • Créez un type Écran, qui aura entre autres comme états une List<PanneauAffichage>. Afficher un Écran signifie afficher ses panneaux du premier au dernier (du plus profond au moins profond); les instances de PanneauAffichage peuvent se chevaucher, dans quel cas le plus « haut » cache (en tout ou en partie) le plus « bas »
    • Lancez des fils d'exécution (des Thread) qui écriront des messages sur vos PanneauAffichage à un rythme de votre choix (chaque Thread peut avoir son propre rythme). Faites en sorte qu'un des fils d'exécution lise du clavier le texte à écrire dans l'un des PanneauAffichage
    • Lancez un fil d'exécution qui sera responsable d'afficher votre Écran. L'affichage devra se faire à rythme fixe (p. ex. : une fois par 500ms ou une fois par seconde), et devra ne remplacer que les cases à l'écran qui ont changé. Note : vous voudrez utiliser Console.SetCursorPosition et Console.ForegroundColor pour arriver à vos fins
    • L'enjeu : l'affichage doit se faire sans corruption 🙂

8 nov.

S19

Au menu :

11 nov.

S20

Au menu :

  • Travail sur le TP02

15 nov.

S21

Au menu :

  • Travail sur le TP02

18 nov.

S22

Au menu :

  • Q05
  • Travail sur le TP02

22 nov.

S23

Au menu :

25 nov.

S24

Au menu :

29 nov.

S25

Au menu :

Exceptionellement, cette séance se fera à distance (par Zoom) utilisant le lien qui vous a été envoyé par Colnet

2 déc.

S26

Au menu :

  • Travail sur la PFI
  • Q07
  • Q08

6 déc.

S27

Au menu :

  • Travail sur la PFI
  • Q06 (oui, je sais...)

9 déc.

S28

À venir

13 déc.

S29

À remettre :

  • La PFI de la session A2021

Chic examen final

Petits coups de pouces

Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous donner un petit coup de pouce occasionnel.

Comment accéder à du code .NET à partir d'un client C++ natif.

Vous trouverez aussi des exemples de code C# dans la section Divers – C# du site, mais notez que je n'ai pas nécessairement harmonisé ces exemples (écrits pour des cours plus avancés, sous forme de survols) aux standards de programmation appliqués dans le présent cours. À lire avec prudence et discrimination, donc.

Consignes des travaux pratiques

Les consignes des travaux pratiques suivent.

Consignes Détails supplémentaires

Activité de révision

Voir S00 et S01

TP00

Pour un programme de test simpliste, voir TP00-code-test-simpliste.html

TP01

Pour un exécutable de démonstration : RaymondCharles.exe

Ils vous faudra aussi, dans le même dossier :

Pour essayer ce démonstrateur, une fois ces quatre fichiers dans un même dossier, allez à la ligne de commande, déplacez-vous dans ce dossier et tapez :

RaymondCharles.exe CarteText.txt

... et ça devrait démarrer le tout.

TP02

Ceci est la version à peu près officielle!

À titre de soutien, si votre TP01 ne fonctionnait pas convenablement (ou s'il ne fonctionnait pas du tout), vous pouvez prendre la mienne dont les sources sont sur TP01-prof-sources.zip et les adapter à vos besoins. Notez que ma petite version maison utilise une classe Identifiant faite « manuellement » alors ajustez le tout pour qu'il utilise votre propre service!

Grâce à mon collègue Marc Beaulne, voici une petite vidéo présentant l'exécution attendue : Démo TP2.mp4

PFI

La .dll dans sa version courante est : ObstacleurService.dll

  • La version courante comprend une mise à jour du fait qu'il arrivant qu'une AucuneOptionException soit levée dans la .dll mais dans un fil d'exécution différent de celui du code appelant (ce qui empêchait de faire un catch dans votre code, avec le modèle d'exceptions de .NET). Je pense que j'ai réglé le bogue, mais revenez-moi si j'ai manqué mon coup!

À titre de soutien, si votre TP02 ne fonctionnait pas convenablement (ou s'il ne fonctionnait pas du tout), vous pouvez prendre la mienne dont les sources sont sur TP02-prof-sources.zip et les adapter à vos besoins. Notez que ma petite version maison utilise une classe Identifiant faite « manuellement » alors ajustez le tout pour qu'il utilise votre propre service. Vous remarquerez qu'il est imparfait (il y a un mini bogue de concurrence au démarrage que je n'ai pas eu le temps de régler, mais ça survient seulement au démarrage et seulement de temps à autres), mais si ça peut vous aider, alors tant mieux!

Petit bonus sur la PFI

Je cite mon illustre collègue Marc Beaulne :

« Question de mettre du piquant à cette PFI, voici un changement que vous pourriez faire à l’implémentation actuelle du TP02. Cette modification vous permettra d’obtenir 5 points en bonis.

Comme vous le savez déjà, chaque obstacle à son propre fil d’exécution qui le fait agir à intervalle régulier. Dans cadre de cette PFI vous devez remplacer ces fils d’exécution associés à chaque obstacle par un seul fil d’exécution qui s’occupera du déplacement de tous les obstacles. Ce fil lancera, de manière asynchrone, une tâche par obstacle à déplacer. L’implémentation demandée doit faire en sorte qu’à intervalle régulier, disons chaque seconde, tous les obstacles se déplacent (ou puissent se déplacer, du moins, vu que vous ne contrôlez pas ce qui se passe dans la .dll).

Ces tâches utiliseront, bien sûr, le service RecevoirDéplacement et bénéficieront de son caractère asynchrone. »

Notez que, pour vous avantager, je compterai ceux qui feront ce bonus (tout à fait optionnel) comme un "minitest" qui remplacera celui où vous aurez eu le plus de difficulté (il se peut donc que la note d'un de vos minitests change dans Colnet, faudra me faire confiance car c'est à votre avantage!)

Solutionnaires

Quelques solutionnaires suivent.

Travail

À venir

À venir

À venir

À venir

En espérant que ça vous aide à organiser vos idées!


Valid XHTML 1.0 Transitional

CSS Valide !