C# – Introduction aux opérateurs

Ceci est un exemple illustrant les propriétés et certains opérateurs en C#. Vous pourrez comparer par vous-mêmes avec des programmes équivalents dans les langages que vous connaissez (ou avec cet exemple en C++).

Cet exemple présente d'abord une ébauche de classe Rationnel, incomplète (amusez-vous à en compléter l'arithmétique en ajoutant addition, multiplication et autres trucs amusants) et un peu incorrecte (la gestion du signe est à revoir, pour ne pas dire à implanter). Une version plus contemporaine est offerte ici.

À remarquer les usages suivants :

  • Les attributs numérateur et dénominateur sont pour usage interne
  • Les propriétés Numérateur et Dénominateur constituent la couche d'abstraction primitive privilégiée en C#. La clause get, équivalent d'un accesseur, représente le code à exécuter lorsqu'une propriété est consultée en lecture, et la clause set (avec un paramètre implicite nommé value), équivalent d'un mutateur, représente le code à exécuter lorsque la propriété est utilisée à gauche d'une affectation
  • Les exceptions sont levées comme en Java, et les classes d'exceptions dérivent typiquement de la classe Exception (utilisée ici, à tort, par souci de simplicité; une classe plus spécialisée, dont le type décrit mieux la réalité du problème rencontré, serait nettement préférable). Notez que les méthodes C# n'annoncent pas les exceptions qu'elles sont susceptibles de lever
  • Les opérateurs sont des méthodes de classe (qualifiées static). Le langage C# est un peu... oppressant, du moins à mes yeux, en ce que pour implémenter ==, il faut aussi implémenter != (ce qui est raisonnable), Equals(object) (ce qui est acceptable) et GetHashCode() (ce qui, sans être inacceptable, est un peu abusif)
  • Les méthodes polymorphiques (qui, comme en C++, doivent être explicitement qualifiées virtual, même si notre exemple ne le montre pas ici), lorsque surchargées, doivent l'être de manière explicite, avec le mot clé override (que vous voyez ici apposé à GetHashCode() et à Equals())

Dans l'implémentation d'Equals(object o), remarquez le test pour valider que le paramètre o soit non nul, et (puisque Equals() prend un object en paramètre) la validation (downcast) que o mène au moins vers un Rationnel, à l'aide de l'opérateur is.

Lorsqu'un objet est utilisé là où une string pourrait être utilisée, C# peut en tirer profit et utiliser, si cela s'avère opportun, la méthode ToString() de l'objet en question. Cette technique est mise en application à quelques reprises dans le programme principal.

Détail très technique : le transtypage se fait comme en C ou en Java, au sens où (T)expr demande de traiter l'expression expr comme étant de type T.

using System;
namespace ExempleOpérateurs
{
   class Rationnel
   {
      // pour faciliter la simplification
      private static int Pgcd(int a, int b)
      {
         return b != 0 ? Pgcd(a, a % b) : a;
      }
      private int dénominateur;
      public int Numérateur
      {
         get; set;
      }
      public int Dénominateur
      {
         get
         {
            return dénominateur;
         }
         set
         {
            if (value == 0)
               throw new Exception("Zut!");
            dénominateur = value;
         }
      }
      public Rationnel(int num, int dénom)
      {
         Numérateur = num;
         Dénominateur = dénom;
      }
      public Rationnel Simplifier()
      {
         int pgcd = Pgcd(Numérateur, Dénominateur);
         return new Rationnel(Numérateur / pgcd, Dénominateur / pgcd);
      }
      public static bool operator ==(Rationnel r0, Rationnel r1)
      {
         return r0.Equals(r1);
      }
      // écrire == oblige d'écrire !=
      public static bool operator !=(Rationnel r0, Rationnel r1)
      {
         return !(r0 == r1);
      }
      // écrire == oblige d'écrire Equals(object)
      // Notez que la surcharge polymorphique doit être explicite
      public override bool Equals(object o)
      {
         if (o == null || !(o is Rationnel))
            return false; // is ≈ dynamic_cast
         Rationnel r0 = Simplifier(),
                   r1 = ((Rationnel)o).Simplifier();
         return r0.Numérateur == r1.Numérateur &&
                r0.Dénominateur == r1.Dénominateur;
      }
      // écrire Equals(object) oblige d'écrire GetHashCode()... >soupir!<
      // Notez que la surcharge polymorphique doit être explicite
      public override int GetHashCode()
      {
         return Numérateur.GetHashCode() ^ Dénominateur.GetHashCode(); // disons
      }
      public override string ToString()
      {
         return Numérateur + "/" + Dénominateur;
      }
   }
   class Program
   {
      static void Main(string[] args)
      {
         Rationnel r0 = new Rationnel(2, 3),
                   r1 = new Rationnel(5, 2);
         Console.WriteLine("{0}, {1}", r0, r1); // utilise Rationnel.ToString()
         Console.WriteLine("{0}", new Rationnel(3, 6).Simplifier());
         r0 = new Rationnel(3, 6);
         r1 = new Rationnel(1, 2);
         Console.WriteLine("{0} {1} {2}", r0, r0 == r1 ? "==" : "!=", r1);
         r0 = new Rationnel(1, 4);
         r1 = new Rationnel(1, 2);
         Console.WriteLine("{0} {1} {2}", r0, r0 == r1 ? "==" : "!=", r1);
      }
   }
}

Lectures complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !