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

21 août

S00

Au menu :

  • Présentation du cours et du plan de cours
  • Les outils dont nous aurons besoin :
    • Nous utiliserons C# 10.0
    • Nous ferons des projets .NET 6
    • 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...) :

// ...
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}");

Après avoir pris un peu de temps pour se « chamailler » avec ce petit défi de remise en forme, je vous ai proposé un peu de code pour vous aider à redémarrer vos instincts de programmeuse et de programmeur. L'accent a été mis sur l'écriture de code simple :

  • Écrire des fonctions
  • Viser « une vocation par fonction »
  • Essayer d'écrire des fonctions qui se limitent à une instruction quand cela s'avère possible
  • ... et se récompenser quand on y parvient, en se donnant le droit d'utiliser la notation => qui est concise et élégante

Le code produit en classe suit :

Program.cs
//
// code produit pour vous aider ce matin et pour amorcer une réflexion avec vous
//
using MonNamespace;
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}");

//
// la fonction qui suit est bien trop compliquée; faudra la revisiter...
//
static bool Trier(ref List<Orque> orques, out int nbPermutations)
{
   nbPermutations = 0;
   Orque[] orq = orques.ToArray(); 
   for (int i = 0; i < orq.Length - 1; ++i)
      for (int j = i + 1; j < orq.Length; ++j)
         if (orq[i].Nom.CompareTo(orq[j].Nom) > 0) // désordre
         {
            Permuter(ref orq[i], ref orq[j]);
            ++nbPermutations;
         }
   orques = orq.ToList();
   return nbPermutations == 0;
}
OutilsTexte.cs
// using...
static class OutilsTexte
{
   const string voyelles = "aeiouy";
   public static bool EstVoyelle(char c) =>
      voyelles.Contains(char.ToLower(c));

   public static int CompterVoyelles(string s)
   {
      int n = 0;
      foreach (char c in s)
         if (EstVoyelle(c))
            ++n;
      return n;
   }
}
Algos.cs
// using...
static class Algos
{
   public static bool EstEntreInclusif(int val, int min, int max) =>
      min <= val && val <= max;
}
Orque.cs
using static MonNamespace.Algos;
using static MonNamespace.OutilsTexte;
// ...
class NomInvalideException : Exception { }

class Orque
{
   const int LG_MAX_NOM = 4;
   static bool EstNomValide(string nom) =>
      EstEntreInclusif(nom.Length, 1, LG_MAX_NOM) &&
      OutilsTexte.CompterVoyelles(nom) <= 1;

   string nom;
   public string Nom
   {
      get => nom;
      private init
      {
            nom = EstNomValide(value) ?
               value : throw new NomInvalideException();
            //if (!EstNomValide(value))
            //   throw new NomInvalideException();
            //nom = value;
      }
   }
   public Orque(string nom)
   {
      Nom = nom;
   }
}

23 août

S01

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 à peu près le suivant). Ce code est perfectible; nous ferons bien mieux plus tard dans la session. Je vous laisse le soin d'écrire le programme principal :

Soldat.cs
// ...
class NomInvalideException : Exception { }
abstract class Soldat
{
   public string Nom { get; private init; }
   public Soldat(string nom)
   {
      Nom = nom;
   }
   public abstract void Saluer();
}
Orque.cs
// ...
class Orque : Soldat
{
   const int LG_MAX_NOM = 4;
   static bool EstNomValide(string s) =>
      EstEntreInclusif(s.Length, 1, LG_MAX_NOM) &&
      CompterVoyelles(s) <= 1;
   static string ValiderNom(string s) =>
      EstNomValide(s) ? s : throw new NomInvalideException();
      public Orque(string nom)
         : base(ValiderNom(nom))
      {
      }
      public override void Saluer()
      {
         Console.WriteLine($"Moi {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 :

Image[] images = new Image[]
{
   new Jpeg(ConsoleColor.Magenta),
   new Png(ConsoleColor.Green),
   new Bmp(ConsoleColor.Blue)
};
// non, pas le droit!
//foreach (Image img in images)
//{
//   img.Dessiner();
//   img = ModifierPeutÊtre(img); // <-- ceci serait illégal
//   img.Dessiner();
//}

for (int i = 0; i != images.Length; ++i)
{
   images[i].Dessiner();
   images[i] = ModifierPeutÊtre(images[i]);
   images[i].Dessiner();
}


////////////////////////

static Image ModifierPeutÊtre(Image img)
{
   // 0 : créer un backup
   Image backup = img.Cloner();

   // 1 : modifier img
   img.Teinte = ConsoleColor.Red;

   // 2 : demander si on veut conserver les modifs
   Console.WriteLine("Conserver les modifs? ");
   // 2a : si oui, on retourne img
   // 2b : sinon, on retourne le backup
   if (Console.ReadKey(true).Key == ConsoleKey.O)
      return img;
   return backup;
}


// il existe une interface ICloneable, qui expose une méthode Clone
// ... mais ne l'utilisez pas :)
abstract class Image
{
   public ConsoleColor Teinte { get; set; }
   public Image(ConsoleColor teinte)
   {
      Teinte = teinte;
   }
   // Idiome NVI : non-virtual interface
   public void Dessiner()
   {
      ConsoleColor pre = Console.ForegroundColor;
      Console.ForegroundColor = Teinte;
      DessinerImpl(); // varie selon les enfants
      Console.ForegroundColor = pre;
   }
   protected abstract void DessinerImpl();
   public abstract Image Cloner();
}
class Jpeg : Image
{
   public Jpeg(ConsoleColor teinte) : base(teinte)
   {
   }
   protected override void DessinerImpl()
   {
      Console.WriteLine($"Jpeg {Teinte}");
   }
   protected Jpeg(Jpeg autre) : base(autre.Teinte)
   {
   }
   // spécialisation covariante
   public override Jpeg Cloner() => new Jpeg(this);
}
class Bmp : Image
{
   public Bmp(ConsoleColor teinte) : base(teinte)
   {
   }
   protected override void DessinerImpl()
   {
      Console.WriteLine($"Bmp {Teinte}");
   }
   protected Bmp(Bmp autre) : base(autre.Teinte)
   {
   }
   public override Bmp Cloner() => new Bmp(this);
}
class Png : Image
{
   public Png(ConsoleColor teinte) : base(teinte)
   {
   }
   protected override void DessinerImpl()
   {
      Console.WriteLine($"Png {Teinte}");
   }
   protected Png(Png autre) : base(autre.Teinte)
   {
   }
   public override Png Cloner() => new Png(this);
}

En fin de séance, nous avons survolé l'énoncé du TP00 et j'ai fait une petite activité dirigée d'un système à deux composants (code client, qui était une application console, et code serveur, qui était une bibliothèque de classes – une DLL).

Le code auquel nous en sommes arrivés pour le client était le suivant (j'ai pris quelques libertés pour vous divertir) :

using Arsenal;
FabriqueArmes fab = new ();
IArme p = fab.CréerArme(Arsenal.Gravité.violent);
p.Frapper();
p = fab.CréerArme(Arsenal.Gravité.délicat);
p.Frapper();

Le code auquel nous en sommes arrivés pour le serveur était le suivant 

namespace Arsenal
{
   public interface IArme
   {
      void Frapper();
   }
   class Masse : IArme
   {
      public void Frapper()
      {
         Console.WriteLine("POURRRRH");
      }
   }
   class Chainsaw : IArme
   {
      public void Frapper()
      {
         Console.WriteLine("FVRRRRRRRR!");
      }
   }
   public enum Gravité { violent, délicat }
   public class FabriqueArmes
   {
      public IArme CréerArme(Gravité grav)
      {
         return grav == Gravité.délicat ? new Masse() : new Chainsaw();
      }
   }
}

Nous avons ensuite survolé les consignes du TP00.

Si votre serveur pour le TP00 fonctionne correctement, le programme de test suivant...

// ... code de test (note : le namespace se nomme Consommateur)
using GénérateurId;
using static Consommateur.Tests;

var fab = new FabriqueGénérateurs();
Test(fab, "Séquentiel", "ID", TypeGénérateur.Séquentiel);
Test(fab, "Recycleur", "ID", TypeGénérateur.Recycleur);
Test(fab, "Aléatoire", "ID", TypeGénérateur.Aléatoire, 3);
Test(fab, "Partagé", "ID", TypeGénérateur.Partagé, 3);
Test(fab, "Recycleur", "ID", TypeGénérateur.Recycleur);
Test(fab, "Partagé", "ID", TypeGénérateur.Partagé);
foreach (var (clé, valeur) in fab.ObtenirStatistiques())
   Console.WriteLine($"{clé} a été instancié {valeur} fois");

// ... placer ce qui suit dans une classe «static» nommée Tests
public static void Test(FabriqueGénérateurs fab, string nom, string préfixe, TypeGénérateur type)
{
   IGénérateurId p = fab.Créer(type, préfixe);
   var lst = new List<Identifiant>();
   Console.Write($"{nom}, pige initiale :\n\t");
   for (int i = 0; i != 10; ++i)
   {
      lst.Add(p.Prendre());
      Console.Write($"{lst[lst.Count - 1]} ");
   }
   Console.WriteLine();
   foreach (var n in lst)
      p.Rendre(n);
   Console.Write($"{nom}, pige post-remise :\n\t");
   for (int i = 0; i != 10; ++i)
      Console.Write($"{p.Prendre()} ");
   Console.WriteLine();
}

public static void Test(FabriqueGénérateurs fab, string nom, string préfixe, TypeGénérateur type, int germe)
{
   IGénérateurId p = fab.Créer(type, préfixe, germe);
   var lst = new List<Identifiant>();
   Console.Write($"{nom}, pige initiale :\n\t");
   for (int i = 0; i != 10; ++i)
   {
      lst.Add(p.Prendre());
      Console.Write($"{lst[lst.Count - 1]} ");
   }
   Console.WriteLine();
   foreach (var n in lst)
      p.Rendre(n);
   Console.Write($"{nom}, pige post-remise :\n\t");
   for (int i = 0; i != 10; ++i)
      Console.Write($"{p.Prendre()} ");
   Console.WriteLine();
}
// ...

... devrait donner un affichage comme le suivant (il peut y avoir certaines différences dans les cas « partagé » et « aléatoire », mais il y a des limites à ces différences – le test utilise un germe choisi – alors consultez votre chic prof si vous avez des doutes) :

Séquentiel, pige initiale :
        ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Séquentiel, pige post-remise :
        ID00010 ID00011 ID00012 ID00013 ID00014 ID00015 ID00016 ID00017 ID00018 ID00019
Recycleur, pige initiale :
        ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Recycleur, pige post-remise :
        ID00009 ID00008 ID00007 ID00006 ID00005 ID00004 ID00003 ID00002 ID00001 ID00000
Aléatoire, pige initiale :
        ID19236 ID45716 ID56688 ID13007 ID36732 ID11833 ID16397 ID62078 ID22853 ID24902
Aléatoire, pige post-remise :
        ID32911 ID53056 ID45554 ID01984 ID05379 ID59249 ID08091 ID56063 ID49708 ID31213
Partagé, pige initiale :
        ID19236 ID45716 ID56688 ID13007 ID36732 ID11833 ID16397 ID62078 ID22853 ID24902
Partagé, pige post-remise :
        ID32911 ID53056 ID45554 ID01984 ID05379 ID59249 ID08091 ID56063 ID49708 ID31213
Recycleur, pige initiale :
        ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Recycleur, pige post-remise :
        ID00009 ID00008 ID00007 ID00006 ID00005 ID00004 ID00003 ID00002 ID00001 ID00000
Partagé, pige initiale :
        ID14127 ID38283 ID32702 ID05286 ID65150 ID29877 ID33647 ID35818 ID24609 ID32588
Partagé, pige post-remise :
        ID19316 ID15965 ID33013 ID04178 ID45250 ID58540 ID33793 ID27893 ID56175 ID43324
Séquentiel a été instancié 1 fois
Recycleur a été instancié 2 fois
Aléatoire a été instancié 1 fois
Partagé a été instancié 1 fois

28 août

S02

Au menu :

  • Propriétés : get, set et init
  • Retour sur l'idiome NVI, survolé à S01
  • Retour sur l'exercice de créer une bibliothèque à liens dynamiques, et d'une 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 officielle du TP00
  • Petit bonbon : introduction aux uplets
  • Travail sur le TP00

30 août

S03

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 :

J'ai mis à votre disposition une petite activité formative, donc qui ne sera pas notée, mais qui est susceptible de vous faire économiser du temps pour les prochains travaux pratiques. Vous trouverez l'énoncé ici : 420KBB--pre-TP01-formatif.pdf

4 sept.

s/o

Fête du travail (jour férié)

6 sept.

S04

Au menu :

  • Lien entre 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 8 septembre

11 sept.

S05

Au menu :

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

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();

//
// 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();
   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");
   }
}

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

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();


// 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();
   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");
   }
}

13 sept.

S06

Au menu :

18 sept.

S07

Au menu :

20 sept.

S08

Au menu :

  • Q01
  • Premiers pas vers une manière plus intelligente de programmer : paramétrer un algorithme de recherche
  • 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!

25 sept.

S09

Au menu :

  • 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
  • Travail sur le TP01

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}");
   }
} 
// ...

27 sept.

S10

Grève étudiante (levée des cours)

2 oct.

S11

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

Travail sur le TP01

4 oct.

S12

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

Travail sur le TP01 (la remise est vendredi le 6 octobre!)

9 oct.

s/o

Action de grâce (jour férié)

10 oct.

S13

Au menu :

  • On se fait une petite collection simpliste :
    • Nous avons fait une liste simplement chaînée de T
    • Nous l'avons ensuite raffinée car certaines de ses fonctions étaient beaucoup trop coûteuses
    • Nous avons ajusté 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.)
  • Nous avons ensuite adapté notre Tableau<T> pour qu'il soit énumérable (qu'il implémente IEnumerable<T>)
  • Retour sur les exercices sur la programmation générique (solutionnaire possible : exercice-apprivoiser-genericite-lambda--solutionnaire.html)
    • Retravailler ces algorithmes pour les rendre plus généraux
  • Introduction aux fonctions variadiques (mot clé params)
  • Introduction aux méthodes d'extension

Prudence : mardi avec horaire du lundi

11 oct.

S14

Au menu :

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

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

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");
}
      
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;
}

Petit exemple inspiré de celui donné en classe (voir https://dotnetfiddle.net/hbR0Iv pour une version en-ligne) :

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");

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);
}

Petit exemple de code qui devrait être rapide mais ne l'est pas... même s'il donne la bonne réponse! (voir https://dotnetfiddle.net/UL4JLB pour une version en ligne mais qui est moins gourmande en mémoire car il y a des limites à ce site) :

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");

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;
}

Petit exemple de code qui devrait être rapide et l'est... avec un tout petit changement! (voir https://dotnetfiddle.net/Jxerel pour une version en ligne, mais qui est moins gourmande en mémoire) :

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");
}
    
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;
}

16 oct.

S15

Au menu :

  • Q02
  • Quelques exemples amusants pour apprivoiser les enjeux de la synchronisation
  • Présentation des zones de transit (attention : code C# vers le bas de la page, le reste est en C++)
  • Exemple avec un pipeline de traitement

Le code du pipeline que nous avons implémenté est, pour l'essentiel :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.IO;
using System.Diagnostics;


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();

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 class Algos
{
   public 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)
         Algos.Permuter(ref lst, ref data);
      return lst;
   }
}

18 oct.

S16

Au menu :

Activité préparatoire au TP02. Soit le code client suivant :

using System;
using System.Threading;

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 (() =>
   {
      var dé = new Random();
      for (int n = 0; !fini; )
      {
         if (dé.Next(0, 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();

class Caméra : IObservateurArdoise
{
   public void Nouveauté(string qui, string quoi)
   {
      Console.WriteLine($"{qui} a dit {quoi}");
   }
}

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 (() =>
      {
         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();
}

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 (() =>
      {
         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();
}

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");
      }
   }
}

23 oct.

S17

Au menu :

  • Q03
  • Travail sur le TP02

25 oct.

S18

Au menu :

  • Travail sur le TP02
  • (je vais attendre à la prochaine séance pour apporter de la nouvelle matière, vu que les cours sont suspendus la semaine prochaine et que de la nouvelle matière risque de « passer dans l'beurre » aujourd'hui)

30 oct.

s/o

Journée de mise à niveau (cours suspendus)

1 nov.

s/o

Journée de mise à niveau (cours suspendus)

6 nov.

S19

Grève du front commun

8 nov.

S20

Au menu :

  • Introduction aux fonctions asynchrones
  • Travail sur le TP02
  • Pendant que vous travaillez, je vais faire progresser la correction (en retard, je sais) du TP01. Je ferai en priorité les copies des gens qui sont en classe avec moi, évidemment

13 nov.

S21

Au menu :

  • Q04
  • Travail sur le TP02
  • À voir pour le reste

15 nov.

S22

Au menu :

  • Q05
  • Travail sur le TP02
  • À voir pour le reste
  • Présentation du (petit) TP03

20 nov.

S23

Au menu :

  • Travail sur le TP03

22 nov.

S24

Au menu : grève du front commun. Votre chic prof est devant le Collège et chante des chansons, pancartes à la main.

27 nov.

S25

Au menu :

  • Q06 (sujet à venir)
  • Explication de l'évaluation du TP03
  • Travail sur le TP03
  • Probablement une présentation de la PFI
  • Explication sommaire des modalités de la PFI

29 nov.

S26

Au menu :

  • Q06 (sujet à venir)
  • Q07 (quelque chose de différent!)
  • Travail sur le TP03

Si vous avez de la difficulté à écrire du code asynchrone, j'ai préparé un petit exemple qui montre le même travail fait de manière séquentielle, puis de manière asynchrone. Notez que c'est du code « à bras » et qu'il y a souvent, à même l'API de la plateforme de votre langage, des version asynchrones de certaines fonctions clés (en particulier, celles qui réalisent des entrées/ sorties).

using System.Diagnostics;

string[] fichiers =
{
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs",
   "../../../Program.cs"
};

var dt0 = Test(() => Séquentiel.Test(fichiers));
Console.WriteLine($"Séquentiel : {dt0} ms");
var dt1 = Test(() => Asynchrone.Test(fichiers));
Console.WriteLine($"Asynchrone : {dt1} ms");

static long Test(Action f)
{
   var sw = new Stopwatch();
   sw.Start();
   f();
   sw.Stop();
   return sw.ElapsedMilliseconds;
}

static class Séquentiel
{
   public static void Test(string[] noms)
   {
      for (int i = 0; i != noms.Length; ++i)
      {
         string s = LireFichier(noms[i]);
         s = Transformer(s, s => s.ToUpper(),
                            s => s.Replace("IF", "SI"));
         ÉcrireFichier(s, $"{noms[i]}{i}.seq.txt");
      }
   }
   public static string LireFichier(string nom)
   {
      using (var sr = new StreamReader(nom))
         return sr.ReadToEnd();
   }
   public static string Transformer(string s, params Func<string, string> [] fcts)
   {
      foreach (var f in fcts)
         s = f(s);
      return s;
   }
   public static void ÉcrireFichier(string s, string nom)
   {
      using (var sw = new StreamWriter(nom))
         sw.Write(s);
   }
}

static class Asynchrone
{
   public static async Task TraiterUn(string nom, int i)
   {
      string s = await LireFichier(nom);
      s = await Transformer(s, s => s.ToUpper(),
                               s => s.Replace("IF", "SI"));
      await ÉcrireFichier(s, $"{nom}{i}.async.txt");
   }
   public static void Test(string[] noms)
   {
      List<Task> tâches = new();
      for (int i = 0; i != noms.Length; ++i)
      {
         tâches.Add(TraiterUn(noms[i], i));
      }
      Task.WaitAll(tâches.ToArray());
   }
   public static async Task<string> LireFichier(string nom)
   {
      using (var sr = new StreamReader(nom))
         return await Task.Run(() => sr.ReadToEnd());
   }
   public static async Task<string> Transformer(string s, params Func<string, string>[] fcts)
   {
      foreach (var f in fcts)
         s = await Task.Run(() => f(s));
      return s;
   }
   public static async Task ÉcrireFichier(string s, string nom)
   {
      using (var sw = new StreamWriter(nom))
         await Task.Run(() => sw.Write(s));
   }
}

À l'exécution, sur mon humble laptop, j'obtiens :

Séquentiel : 26 ms
Asynchrone : 13 ms

... mais les résultats peuvent varier selon les fichiers, le contexte, la charge de votre ordinateur, etc.

4 déc.

S27

Au menu :

  • On fait la PFI (soyez prêtes, soyez prêts!)

6 déc.

S28

Au menu : séance de tutorat à mon bureau (si vous avez des questions de dernière minute avant l'examen final...)

11 déc.

S29

Au menu :

  • 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 À remettre...

Activité de révision

Voir S00 et S01

s/o

TP00

Code de test proposé à la séance S01

8 septembre 23 h 59

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 CarteTest.txt

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

6 octobre 23 h 59

TP02

La .dll nommée RaymondCharlesServicesBase.dll : RaymondCharlesServicesBase.dll

  • Note : plus récente mise à jour le 24 octobre 2023 àh 38

Le code du programme principal est :

using RaymondCharlesClientMT;
using RaymondCharlesServicesBase;

var fab = new FabriqueCarte(() => new List<char>() { Participant.SYMBOLE },
                            () => new List<char>() { Participant.SYMBOLE },
                            () => new List<char>()
                            {
                               Carte.Symboles.MUR,
                               Carte.Symboles.VIDE,
                               Participant.SYMBOLE
                            });
var carte = fab.Créer(args.Length == 0 ?
   "../../../CarteTest.txt" : args[0]);
PanneauAffichage menu =
   FabriquePanneau.Créer(carte, 4, 80, ConsoleColor.DarkCyan);
var raymond = new Participant(carte.Trouver(Participant.SYMBOLE)[0], menu);
SorteDirecteur sorte = args.Length >= 2 ?
   FabriqueDirecteur.TraduireSorte(args[1]) : SorteDirecteur.humain;
var (diag, écran, obst) =
   Organisateur.Organiser(carte, raymond, sorte, menu);
while (diag.Poursuivre(carte, raymond))
{
   var choix = diag.Analyser(raymond.Agir(carte), raymond, carte);
   carte.Appliquer(raymond, choix);
}
obst.Terminer();
menu.Write(diag.ExpliquerArrêt(), ConsoleColor.Yellow);
écran.Arrêter(); // arrêt de l'affichage
Console.ResetColor();

Vendredi 17 novembre 23 h 59

TP03

Un petit travail pratique pour apprivoiser les fonctions asynchrones... et se divertir!

Vendredi 1 décembre 23 h 59

PFI

À venir

À venir

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 !