2.2. C# et polymorphisme d'objet 


Plan général:   ...........retour au plan général

Le polymophisme en C#  : Rappel des notions de base

    Polymorphisme d'objet en C#  :  définitions
    1.1 Instanciation et utilisation dans le même type
    1.2 Polymorphisme d'objet implicite
    1.3
    Polymorphisme d'objet explicite par transtypage
    1.4 Utilisation pratique du polymorphisme d'objet
    1.5 Instanciation dans un type ascendant impossible


Le polymorphisme en C#

Rappel essentiel sur les notions de bases

Il existe un concept essentiel en POO désignant la capacité d'une hiérarchie de classes à fournir différentes implémentations de méthodes portant le même nom et par corollaire la capacité qu'ont des objets enfants de modifier les comportements hérités de leur parents. Ce concept d'adaptation à différentes "situations" se dénomme le polymorphisme qui peut être implémenté de différentes manières.

Polymorphisme d'objet 

C'est une interchangeabilité entre variables d'objets de classes de la même hiérarchie sous certaines conditions,  que dénommons le polymorphisme d'objet.
 



Polymorphisme par héritage de méthode 

Lorsqu'une classe enfant hérite d'une classe mère, des méthodes supplémentaires nouvelles peuvent être implémentées dans la classe enfant mais aussi des méthodes des parents peuvent être substituées pour obtenir des implémentations différentes.
 


Polymorphisme par héritage de classes abstraites 

Une classe abstraite est une classe qui ne peut pas s'instancier elle-même ; elle doit être héritée. Certains membres de la classe peuvent ne pas être implémentés, et c'est à la classe qui hérite de fournir cette implémentation.
 


Polymorphisme par implémentation d'interfaces 

Une interface décrit la signature complète des membres qu'une classe doit implémenter, mais elle laisse l'implémentation de tous ces membres à la charge de la classe d'implémentation de l'interface.
 

Remonter


  Polymorphisme d'objet en C#


Soit une classe Mere et une classe Fille héritant de la classe Mere :

Les objets peuvent avoir des comportements polymorphes (s'adapter et se comporter différement selon leur utilisation) licites et des comportements polymorphes dangereux selon les langages.

Dans un langage dont le modèle objet est la référence (un objet est un couple : référence, bloc mémoire) comme  C#,  il y a découplage entre les actions statiques du compilateur et les actions dynamiques du système d'exécution,  le compilateur protège statiquement des actions dynamiques sur les objets une fois créés. C'est la déclaration et l'utilisation des variables de références qui autorise ou non les actions licites grâce à la compilation.

Supposons que nous ayons déclaré deux variables de référence, l'une de classe Mere, l'autre de classe Fille, une question qui se pose est la suivante : au cours du programme quelle genre d'affectation et d'instanciation est-on autorisé à effectuer sur chacune de ces variables dans un programme C#.

En C# :

public class Mere {
.....

public class Fille : Mere {
.....

 


L'héritage permet une variabilité entre variables d'objets de classes de la même hiérarchie, c'est cette variabilité que dénommons le polymorphisme d'objet.

Nous allons dès lors envisager toutes les situations possibles et les évaluer, les exemples explicatifs sont écrits en C# (lorsqu'il y a discordance avec java ou Delphi autres langages, celle-ci est mentionnée explicitement), il existe 3 possibilités différentes illustrées par le schéma ci-dessous.


L'instanciation et l'utilisation de références dans le même type
L'affectation de références :  polymorphisme implicite

L'affectation de références :   polymorphisme par transtypage d'objet

La dernière de ces possibilités pose un problème d'exécution lorsqu'elle mal employée !



1.1 instanciation dans le type initial et utilisation dans le même type

Il s'agit ici d'une utilisation la plus classique qui soit, dans laquelle une variable de référence d'objet est utilisée dans son type de définition initial (valable dans tous les LOO)



En C# :

Mere x , u ;
Fille y , w ;
.....
x  = new Mere( ) ; // instanciation dans le type initial
u = x ; // affectation de références du même type

y = new Fille( ) ;  // instanciation dans le type initial
v = y ; // affectation de références du même type
 



1.2 Polymorphisme d'objet implicite




En C# :

Mere x ;
Fille  ObjF = new  Fille( )   ;
x = ObjF; // affectation de références du type descendant implicite
 

Nous pouvons en effet dire que x peut se référer implicitement à tout objet de classe Mere ou de toute classe héritant de la classe Mere.
 
 

fig - 1


fig - 2

 
Dans la figure fig-1 ci-dessus, une hiérarchie de classes decendant toutes de la classe Mere, dans fig-2 ci-contre le schéma montre une référence de type Mere qui peut 'pointer' vers n'importe quel objet de classe descendante (polymorphisme d'objet).

D'une façon générale vous pourrez toujours écrire des affectations entre deux références d'objets :

En C# :

  Classe1  x ;
  Classe2  y ;
  ..........
  x = y ;
si et seulement si Classe2 est une classe descendante de Classe1.
 


Exemple pratique tiré du schéma précédent

1°) Le polymorphisme d'objet est typiquement fait pour représenter des situations pratiques figurées ci-dessous :

Une hiérarchie de classe de véhicules descendant toutes de la classe mère Vehicule,  on peut énoncer le fait suivant :
Un véhicule peut être de plusieurs sortes : soit un croiseur, soit une voiture, soit un véhicule terrestre
etc...

Traduit en termes informatiques, si l'on déclare une référence de type véhicule (vehicule x) elle pourra pointer vers n'importe quel objet d'une des classe filles de la classe vehicule.

En C# :

public class Vehicule {
  ..........
} 
public class
terrestre : Vehicule{  

 ..........
} 
public class voiture : terrestre {
  ..........
}   

 
public class
marin : Vehicule {  
 ..........
}

public class voilier : marin {
  ..........
}  

public class
croiseur : marin {  
 ..........
} 

 
Mettons en oeuvre la définition du polymorphisme implicite :

Polymorphisme implicite =  création d'objet de classe descendante référencé par une variable parent

Ajoutons 2 classes à la hiérarchie des véhicules :

Partons de la situation pratique suivante :

Traduit en termes informatiques : nous déclarons 3 références x, y et z de type vehicule, voiture et break et nous créons 3 objets de classe voiture, berline et break.

Comme il est possible de créer directement un objet de classe descendante à partir d'une référence de classe mère, nous proposons les instanciations suivantes :



En C# :

public class berline : voiture {
  ..........
}  

public class 
break : voiture {  
 ..........
}
 

public class Fabriquer {
   Vehicule x = new voiture ( ) ;
    voiture y = new berline ( ) ;
    break z = new break ( );
  ..........
}  


1.3 Polymorphisme d'objet explicite par transtypage

La situation informatique est la suivante :

Il est alors possible de faire "pointer" la variable y (de type Fille) vers l'objet (de type Fille) auquel se réfère x en effectuant une affectation de références :

y = x ne sera pas acceptée directement car statiquement les variables x et y ne sont pas du même type, il faut indiquer au compilateur que l'on souhaite temporairement changer le type de la variable x afin de pouvoir effectuer l'affectation.
Cette opération de changement temporaire, se dénomme le transtypage ( notée en C# : y = (Fille)x ) :



En C# :

Mere x ;
Fille y  ; 

Fille  ObjF = new  Fille( )   ;
x = ObjF ; // x pointe vers un objet de type Fille
y = (Fille) x ; // transtypage et affectation de références du type ascendant explicite compatible dynamiquement.
 


Attention

  • La validité du transtypage n'est pas vérifiée statiquement par le compilateur, donc si votre variable de référence pointe vers un objet qui n'a pas la même nature que l'opérateur de transtypage, c'est lors de l'exécution qu'il y aura production d'un message d'erreur indiquant le transtypage impossible.
  •  
  • Il est donc impératif de tester l'appartenance à la bonne classe de l'objet à transtyper avant de le transtyper, les langages C#, Delphi et Java disposent d'un opérateur permettant de tester cette appartenance ou plutôt l'appartenance à une hiérarchie de classes (opérateur is en C#).

  • L'opérateur as est un opérateur de transtypage de référence d'objet semblable à l'opérateur ( ). L'opérateur as fournit la valeur null en cas d'échec de conversion alors que l'opérateur ( ) lève une exception.

 


En C# :

Mere x   ;
Fille y   ;

x = new  Mere( ) ; // instanciation dans le type initial
{ affectation de références du type ascendant explicite mais dangereuse si x est uniquement Mere : }
 y = (Fille)x ; 
<--- erreur lors de l'exécution ici
{
affectation acceptée statiquement mais refusée dynamiquement, car x pointe vers un objet de type Mere }
 


En reprenant l'exemple pratique précédant de la hiérarchie des véhicules :

Puisque x pointe vers un objet de type voiture toute variable de référence acceptera de pointer vers cet objet, en particulier la variable voiture après transtypage de la référence de x.

En C# l'affectation s'écrirait par application de l'opérateur de transtypage :

y = ( voiture ) x;


Pour pallier à cet inconvénient de programmation pouvant lever des exceptions lors de l'exécution, C# offre au programmeur la possibilité de tester l'appartenance d'un objet référencé par une variable quelconque à une classe ou plutôt une hiérarchie de classe; en C# cet opérateur se dénote is :


L'opérateur  "is" de C# est identique à celui de Delphi :
 
L'opérateur is, qui effectue une vérification de type dynamique, est utilisé pour vérifier quelle est effectivement la classe d'un objet à l'exécution. 

L'expression :  objet   is   classeT

renvoie True si objet est une instance de la classe désignée par classeT ou de l'un de ses descendants, et False sinon. Si objet a la valeur nil, le résultat est False.
 

 

En C# :

Mere x   ;
Fille y   ;

x = new  Mere( ) ; // instanciation dans le type initial
if ( x is Fille) // test d'appartenance de l'objet référencé par x à la bonne classe
   y = (Fille)x ;

 



1.4 Utilisation pratique du polymorphisme d'objet

Le polymorphisme d'objet associé au transtypage est très utile dans les paramètres des méthodes.

Lorsque vous déclarez une méthode meth avec un paramètre formel x de type ClasseT :

void  meth ( ClasseT x );
{
    ........
}

Vous pouvez utiliser lors de l'appel de la méthode meth n'importe quel paramètre effectif de ClasseT ou bien d'une quelconque classe descendant de ClasseT et ensuite à l'intérieur de la procédure vous transtypez le paramètre. Cet aspect est utilisé en particulier en C# lors de la création de gestionnaires d'événements communs à plusieurs composants :

private
void meth1(object sender, System.EventArgs e) {
   if (sender is System.Windows.Forms.TextBox)
             (sender as TextBox).Text="Fin"; 
  else if
(sender is System.Windows.Forms.Label)
                (sender as Label).Text="ok"; 

  // ou encore :

  if (sender is System.Windows.Forms.TextBox)
      ( (TextBox)sender ).Text="Fin";

  else if (sender is System.Windows.Forms.Label)
                ( (Label)sender ).Text="ok"; 
} 

Autre exemple avec une méthode meth2 personnelle sur la hiérarchie des véhicules :

private void  meth2 ( vehicule Sender );
{
     if (Sender is voiture) 
       ((voiture)Sender).  ....... ;
    else if (Sender is voilier) 
      ((voilier)Sender).  ....... ;
   ............
}



1.5 instanciation dans un type ascendant (Dangereux, déconseillé et impossible en C#)

Il s'agit ici d'une utilisation non licite qui n'est pas commune à tous les langages LOO.

Le compilateur C# comme le compilateur Java, refuse ce type de création d'objet, les compilateurs C++ et Delphi acceptent ce genre d'instanciation en laissant au programmeur le soin de se débrouiller avec les problèmes de cohérence lorsqu'ils apparaîtront.

Remonter