C# – Clonage et transtypage

Ceci est un exemple implémentant (de manière simpliste) le clonage en C#. Vous pourrez comparer par vous-mêmes avec des programmes équivalents dans les langages que vous connaissez.

Pour les fins de notre exemple, nous utiliserons des formes géométriques, qui seront susceptibles d'être affichées (d'où Dessinable) et qui sauront se dupliquer subjectivement au besoin (d'où Clonable).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Exemple08Clonage
{
   public interface Clonable
   {
      Clonable Cloner();
   }
   public interface Dessinable
   {
      void Dessiner();
   }

Une Forme sera à la fois Dessinable et Clonable. C#, comme Java, supporte l'héritage multiple d'interfaces mais l'héritage simple d'implémentation.

Notez ici que, puisque nous avons une classe qui dérive d'interfaces sans implémenter leurs services, nous devons (en C#) qualifier explicitement cette classe d'abstraite et répéter que les méthodes de ces interfaces sont, à ce stade, encore abstraites.

C'est un peu verbeux, mais bon...



// ...
   public abstract class Forme
      : Dessinable, Clonable
   {
      // répétition obligatoire... Ugh!
      public abstract Clonable Cloner();
      public abstract void Dessiner();
   }

Pour les fins de l'exemple, lorsqu'une sorte de Forme sera instanciée avec une taille jugée illégale, nous lèverons une exception de type TailleInvalide.

// ...
   public class TailleInvalide
      : ApplicationException
   {
   }

Les deux exemples de dérivés de Forme que nous utiliserons sont proposés à droite, et sont d'une grande banalité. Remarquez tout de même la surcharge de Cloner(), qui n'est pas covariante sur le type de retour (détail d'une grande tristesse), ce qui forcera le code client à réaliser des transtypages (des casts) conceptuellement superflus.



// ...
   public class Carré
      : Forme
   {
      private int taille;
      public int Taille
      {
         get
         {
            return taille;
         }
         set
         {
            if (value <= 0)
               throw new TailleInvalide();
            taille = value;
         }
      }
      // pas de covariance... snif!
      public override Clonable Cloner()
      {
         return new Carré(this);
      }
      protected Carré(Carré c)
      {
         Taille = c.Taille;
      }
      public Carré(int n)
      {
         Taille = n;
      }
      public override void Dessiner()
      {
         for (int i = 1; i <= Taille; ++i)
         {
            for (int j = 1; j <= Taille; ++j)
               Console.Write("*");
            Console.WriteLine();
         }
      }
   }
   public class Triangle
      : Forme
   {
      private int taille;
      public int Taille
      {
         get
         {
            return taille;
         }
         set
         {
            if (value <= 0)
               throw new TailleInvalide();
            taille = value;
         }
      }
      // pas de covariance... snif!
      public override Clonable Cloner()
      {
         return new Triangle(this);
      }
      protected Triangle(Triangle t)
      {
         Taille = t.Taille;
      }
      public Triangle(int n)
      {
         Taille = n;
      }
      public override void Dessiner()
      {
         for (int i = 1; i <= Taille; ++i)
         {
            for (int j = 1; j <= i; ++j)
               Console.Write("*");
            Console.WriteLine();
         }
      }
   }

Pour des fins d'expérimentation quant à la covariance potentielle sur la base de la généricité, nous utiliserons aussi plus bas la classe générique Afficher<T>, où T doit être Dessinable (donc où Afficher aurait pu ne pas être générique et où ça n'aurait pas changé grand chose.

Si nous avions voulu imposer le respect de plus d'une interface, cependant, la généricité aurait pu être utile.



// ...
   class Afficher<T>
      where T : Dessinable
   {
      private T objet;
      public Afficher(T obj)
      {
         objet = obj;
      }
      public void Exécuter()
      {
         objet.Dessiner();
      }
   }

Notre programme exemple invoque des services de dessin et de clonage montrant au passage des particularités du système de types de C# (examinez le 2e bloc try, qui réalise un transtypage frère-soeur) et en montre les limites (absence de covariance sur la généricité et sur les types de retour des méthodes polymorphiques).



// ...
   class Program
   {
      static void Main(string[] args)
      {
         Forme f = new Triangle(3);
         Forme f2 = (Forme) f.Cloner();
         f.Dessiner();
         f2.Dessiner();
         try
         {
            Carré c = (Carré)f2;
         }
         catch (InvalidCastException e)
         {
            Console.WriteLine(e.Message);
         }
         try
         {
            Dessinable d = (Dessinable)f2;
            d.Dessiner();
            Clonable c = (Clonable)d;
            ((Triangle)c.Cloner()).Dessiner();
         }
         catch (InvalidCastException e)
         {
            Console.WriteLine(e.Message);
         }
         afficher<Triangle> a_t = new Afficher<Triangle>((Triangle) f);
         a_t.Exécuter();
         // pas de covariance...
         //afficher<Forme> a_f = a_t;
         //a_f.Exécuter();
      }
   }
}

Valid XHTML 1.0 Transitional

CSS Valide !