2.4. C# polymorphisme et interfaces 



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

Interfaces en C#

    Rappels sur la notion d'interface

    1.Concepts et vocabulaire d'interface en C#

    1.1
    Spécification d'un exemple complet
1.1.A Une classe abstraite
1.1.B
Une interface
1.1.C Une simulation d'héritage multiple
1.1.D Encore une classe abstraite, mais plus concrète
1.1.E Une classe concrète

    1.2 Implantation en C# de l'exemple

1.2.A La classe abstraite
1.2.B
L'interface
1.2.C La simulation d'héritage multiple
1.2.D La nouvelle classe abstraite
1.2.E La classe concrète

    2.
    Analyse du code de liaison de la solution précédente

2.1 Le code de la classe Vehicule
2.2 Le code de l'interface IVehicule
2.3 Le code de la classe UnVehicule
2.4 Le code de la classe Terrestre
2.5 Le code de la classe Voiture
  3. Cohérence de C# entre les notions de classe et d'interface



Interface en C#


Rappels essentiels sur la notion d'interface


Quelques conseils généraux prodigués par des développeurs professionnels (microsoft, Borland, Sun) :


1. Vocabulaire et concepts en C# : 

 
  • Une interface est un contrat, elle peut contenir des propriétés, des méthodes , des événements ou des indexeurs, mais ne doit contenir aucun champ ou attribut
  • Une interface ne peut pas contenir des méthodes déjà implémentées. 
  • Une interface ne contient que des signatures. 
  • Tous les membres d'une interface sont automatiquement public.
  • Une interface est héritable.
  • On peut construire une hiérarchie d'interfaces.
  • Pour pouvoir construire un objet à partir d'une interface, il faut définir une classe non abstraite implémentant tous les membres de l'interface.


Les interfaces peuvent constituer des hiérarchies et hériter entre elles

soient l'interface IinterfA et l'interface IinterfB héritant de IinterfA . On pourra employer aussi le vocable d'étendre au sens où l'interface dérivée IinterfB "étend" le contrat (augmente le nombre de membres contractuels) de l'interface IinterfA .

Dans tous les cas il faut une classe pour implémenter ces contrats :

 
code C# :

interface  IinterfA {
   event 
 TypEvent1 OnTruc ;  // événement
   
 
char  Prop1    //  propriété
   
{       get ;       set ;   }
   void 
meth1 ( ); //  méthode
   int  meth2 ( ); //  méthode
   
char  meth3 ( int x ); //  méthode
}

interface  IinterfB :  IinterfA {
   event 
 TypEvent2 OnChose ;  // événement
   
string  Prop2    //  propriété
   
{       get ;     }
   void 
meth4 ( ); //  méthode
}




La construction d'un objet necessite une classe implémentant l'interface

La classe ClasseY doit implémenter tous les 8 membres provenant de l'héritage des interfaces :  les 2 événements OnTruc et Onchose, les 2 propriétés Prop1 et Prop2, et enfin les 4 méthodes meth1, ... , meth4 . La classe ClasseY est une classe concrète (instanciable), un objet de cette classe possède en particulier tous les membres de l'interface IinterfB (et donc IinterfA car IinterfB hérite de IinterfA)

class ClasseY : ClasseX , IinterfB {
  //  ... implémente tous les membres de InterfB
}
//  construction et utilisation d'un objet :
ClasseY Obj = new ClasseY( ) ;
Obj.Prop1 =  'a' ; //  propriété  héritée de  InterfA
string s = Obj.prop2 ; //  propriété  héritée de  InterfB
Obj.meth1( ) ; //  méthode  héritée de  InterfA
etc ...

Si, ClasseY n'implémente par exemple que 7 membres sur les 8 alors C# considère que c'est une classe abstraite et vous devez la déclarer abstract :

abstract class ClasseY : ClasseX , IinterfB {
  //  ...n' implémente que certains membres de InterfB
}
class ClasseZ : ClasseY  {
  //  ... implémente le reste des membres de InterfB
}
//  ... construction d'un objet :
ClasseZ Obj = new ClasseZ( ) ;
Obj.Prop1 =  'a' ; //  propriété  héritée de  InterfA
string s = Obj.prop2 ; //  propriété  héritée de  InterfB
Obj.meth1( ) ; //  méthode  héritée de  InterfA
etc ...


Les implémentations des membres d'une interface sont en général public

Par défaut sans déclaration explicite, les membres (indexeurs, propriétés, événements, méthodes) d'une interface ne nécessitent pas de qualificateurs de visibilité car ils sont automatiquement déclarés par C# comme étant de visibilité public, contrairement à une classe ou par défaut les membres sont du niveau assembly.

Ce qui signifie que toute classe qui implémente un membre de l'interface doit obligatoirement le qualifier de public sous peine d'avoir un message d'erreur du compilateur, dans cette éventualité le membre devient un membre d'instance. Comme la signature de la méthode n'est qu'un contrat le mode de liaison du membre n'est pas fixé; la classe qui implémente le membre peut alors choisir de l'implémenter soit en liaison statique, soit en liaison dynamique.

Soient une interface IinterfA et une classe ClasseX héritant directement de la classe Object et  implémentant cette interface, ci-dessous les deux seules implémentations possibles d'une méthode avec un rappel sur les redéfinitions possibles dans des classes descendantes :

interface  IinterfA {
   void  meth1 ( ); //  méthode de l'interface
}


Implémentation en liaison précoce
Implémentation en liaison tardive
meth1 devient  une méthode d'instance
class ClasseX :  IinterfA {
  public void  meth1 ( ){ ... }
}

class ClasseX :  IinterfA {
  public virtual void  meth1 ( ){ ... }
}

Redéfinitions possibles
Redéfinitions possibles
class ClasseY :  ClasseX {
  public new void  meth1 ( ){ ... }
 masque statiquement celle de ClasseX
}

class ClasseZ :  ClasseX {
  public new virtual void  meth1 ( ){ ... }
 masque dynamiquement celle de ClasseX
}
class ClasseY :  ClasseX {
  public new void  meth1 ( ){ ... }
  masque statiquement celle de ClasseX
}

class ClasseZ :  ClasseX {
  public new virtual void  meth1 ( ){ ... }
  masque dynamiquement celle de ClasseX
}

class ClasseT :  ClasseX {
  public override void  meth1 ( ){ ... }
  redéfinit dynamiquement celle de ClasseX
}


Les implémentations explicites des membres d'une interface sont spéciales

Une classe qui implémente une interface peut aussi implémenter de façon explicite un membre de cette interface. Lorsqu'un membre est implémenté de façon explicite  (le nom du membre est préfixé par le nom de l'interface :  InterfaceXxx.NomDuMembre ), il n'est pas accessible via une référence de classe, il est alors invisible à tout objet instancié à partir de la classe où il est défini. Un membre implémenté de façon explicite n'est donc pas un membre d'instance.

Pour utiliser un membre d'interface implémenté de manière explicite, il faut utiliser une référence sur cette interface et non une référence de classe; il devient visible uniquement à travers une référence sur l'interface.

Nous reprenons le même tableau de différentes implémentations de la méthode void  meth1 ( ) en ajoutant une nouvelle méthode void  meth2 ( int  x ) que nous implémentons explicitement dans les classes dérivées :

interface  IinterfA {
   void  meth2 ( int  x ); //  méthode de l'interface
   void  meth1 ( ); //  méthode de l'interface
}


Nous implémentons l'interface IinterfA dans la classe ClasseX :
1°) nous implémentons  explicitement void  meth2 ( int  x ),
2°) nous implémentons void  meth1 ( ) en méthode virtuelle.

interface  IinterfA {
   void  meth2 ( int  x ); //  méthode de l'interface
   void  meth1 ( ); //  méthode de l'interface
}

class ClasseX :  IinterfA {
  void  IinterfA.meth2 ( int  x ){ ... }
  public virtual void  meth1 ( ){ ... }
}

Comprenons bien que la classe ClasseX ne possède pas à cet instant une méthode d'instance qui se nommerait meth2, par exemple si dans la méthode virtuelle meth1 nous utilisons le paramètre implicite this qui est une référence à la future instance, l'audit de code de C#Builder nous renvoie 7 méthodes comme visibles (6 provenant de la classe mère Object et une seule provenant de ClasseX), la méthode  IinterfA.meth2 n'est pas visible :



Lorsque l'on instancie effectivement un objet de classe ClasseX, cet objet ne voit comme méthode provenant de ClasseX que la méthode meth1 :



La méthode meth2 implémentée explicitement en IinterfA.meth2 devient visible uniquement si l'on utilise une référence sur l'interface IinterfA, l'exemple ci-dessous montre qu'alors les deux méthodes meth1 et meth2 sont visibles :




L'audit de code de Visual C# fournit plus de précision directement :



Nous voyons bien que la méthode est qualifiée avec sa signature dans IinterfA, voyons dans l'exemple ci-dessous que nous pouvons déclarer une méthode d'instance ayant la même signature que la méthode explicite, voir même de surcharger cette méthode d'instance sans que le compilateur C# n'y voit de conflit car la méthode explicite n'est pas rangé dans la table des méthodes d'instances de la classe :

class ClasseX :  IinterfA {
  void  IinterfA.meth2 ( int  x ){ ... }    //méthode de IinterfA implémentée explicitement
  public virtual void  meth2 ( int  x ){ ... }  //méthode de la ClasseX surchargée
  public virtual void  meth2 ( char  c ){ ... } //méthode de la ClasseX surchargée
  public virtual void  meth1 ( ){ ... } //méthode de IinterfA implémentée virtuellement
}



La référence Obj1 peut appeller les deux surcharges de la méthode d'instance meth2 de la classe ClasseX :



La référence Obj2 sur IinterfA fonctionne comme nous l'avons montré plus haut, elle ne peut voir de la méthode meth2 que son implémentation explicite :



Cette fonctionnalité d'implémentation explicite spécifique à C# peut être utilisée dans au moins deux cas utiles au développeur :




1.1 Spécification d'un exemple complet

Utilisons la notion d'interface pour fournir un polymorphisme à une hiérarchie de classe de véhicules  fondée sur une interface. :

Soit au départ une classe abstraite Vehicule et une interface IVehicule.


1.1.A) Une classe abstraite


La classe abstraite Vehicule contient trois méthodes :



1.1.B) Une interface


Afin d'utiliser les possibilités de C#, l'interface IVehicule propose un contrat d'implémentation pour un événement, un indexeur, une propriété et une méthode :




1.1.C) Une simulation d'héritage multiple

Nous souhaitons construire une classe abstraite UnVehicule qui "hérite" à la fois des fonctionnalités de la classe Vehicule et de celles de l'interface IVehicule. Il nous suffit en C# de faire hériter la classe UnVehicule de la classe Vehicule , puis que la classe UnVehicule implémente les propositions de contrat de l'interface IVehicule :




1.1.D) Encore une classe abstraite, mais plus "concrète"

Nous voulons maintenant proposer une spécialisation du véhicule en créant une classe abstraite Terrestre , base des futurs véhicules terrestres. Cette calsse implantera de façon explicite la méthode RépartirPassagers de répartition des passagers et la méthode PériodicitéMaintenance renvoyant la périodicité de la maintenance. Cette classe Terrestre reste abstraite car elle ne fournit pas l'implémentation de la méthode Demarrer :




1.1.E) Une classe concrète

Nous finissons notre hiérarchie par une classe Voiture qui descend de la classe Terrestre , qui implante la méthode Demarrer( ) et qui redéfinie la méthode Stopper( ) :



Ce qui nous donne le schéma d'héritage total suivant :





1.2 Implantation en C# de l'exemple

       Nous proposons ci-dessous pour chaque classe ou interface une implémenation en C#.

1.2.A) La classe abstraite Vehicule

abstract class  Vehicule  // classe abstraite mère
{
   public abstract void 
Demarrer ( ); // méthode abstraite
   public void  RépartirPassagers ( )  {  } // implantation de méthode avec corps vide
   public void  PériodicitéMaintenance ( ) {  } // implantation de méthode avec corps vide
}



1.2.B) L'interface IVehicule



public delegate void  Starting ( );    //  declaration de type délégué

interface  IVehicule
{
   event 
Starting OnStart ;   // déclaration d'événement du type délégué : Starting
    string  this  [ int  index]    // déclaration d'indexeur
   
{
      
get ;
      
set ;
   }
   
string  TypeEngin    // déclaration de propriété
   
{
      
get ;
      
set ;
   }
   void 
Stopper ( ); // déclaration de méthode
}



1.2.C) La classe UnVehicule

abstract class  UnVehicule : Vehicule , IVehicule  // hérite de la classe mère et implémente l'interface
{
   private 
string  nom = "";
   private 
string [ ] ArrayInfos = new  string [10] ;
   public  event 
Starting OnStart ;
   protected void 
LancerEvent ()
   {
      if(
OnStart  !=  null)
         
OnStart ();
   }
   public 
string  this  [ int  index]    // implantation Indexeur
   
{
      
get        {         return  ArrayInfos[index] ;       }
      
set       {          ArrayInfos[index] = value ;       }
   }
   public 
string  TypeEngin    // implantation propriété
   
{
      
get       {         return  nom ;       }
      
set       {          nom = value ;       }
   }
   public virtual void 
Stopper ( )   {  } // implantation de méthode avec corps vide
}



1.2.D) La classe Terrestre

abstract class  Terrestre  UnVehicule  
{
   public new void 
RépartirPassagers ( )    {
      
//...implantation de méthode
   }
   public new void 
PériodicitéMaintenance ( )  {
  
  //...implantation de méthode
   }
}



1.2.E) La classe Voiture

class  Voiture  Terrestre  
{
   public override void  
Demarrer ( )  {
      
LancerEvent ( );
   }
   public override void 
Stopper ( ) {
      
//...
   
}
 }





2. Analyse du code de liaison de la solution précédente

Nous nous interessons au mode de liaison des membres du genre :

méthodes, propriétés, indexeurs et événements.

Rappelons au lecteur que la liaison statique indique que le compilateur lie le code lors de la compilation, alors que dans le cas d'une liaison dynamique le code n'est choisi et lié que lors de l'exécution.


2.1 Le code de la classe Vehicule

abstract class  Vehicule  
{
   public abstract void 
Demarrer ( ); // méthode abstraite
   public void  RépartirPassagers ( )  {  } // implantation de méthode avec corps vide
   public void  PériodicitéMaintenance ( ) {  } // implantation de méthode avec corps vide
}


Analyse :
Sans qualification particulière une méthode est à liaison statique :
  • La méthode public void  RépartirPassagers ( )  est donc à laison statique.
  • La méthode public void  PériodicitéMaintenance ( )  est donc à laison statique.

Une méthode qualifiée abstract est implicitement virtuelle :
  • La méthode public abstract void  Demarrer ( )  est donc à laison dynamique.



2.2 Le code de l'interface IVehicule

interface  IVehicule
{
   event 
Starting OnStart ;   // déclaration d'événement du type délégué : Starting
    string  this  [ int  index]    // déclaration d'indexeur
   
{       get ;       set ;   }
   
string  TypeEngin    // déclaration de propriété
   
{       get ;       set ;   }
   void 
Stopper ( ); // déclaration de méthode
}


Analyse :
Une interface n'est qu'un contrat, les membres déclarés comme signatures dans l'interface n'étant pas implémentées, la question de leur liaison ne se pose pas au niveau de l'interface, mais lors de l'implémentation dans une classe ultérieure :
  • La méthode void  Stopper ( ); pourra donc être plus tard soit statique, soit dynamique.
  • L'événement event  Starting OnStart ; pourra donc être plus tard soit statique, soit dynamique.
  • La propriété string  TypeEngin , pourra donc être plus tard soit statique, soit dynamique.
  • L'indexeur string  this  [ int  index] , pourra donc être plus tard soit statique, soit dynamique.



2.3 Le code de la classe UnVehicule

abstract class  UnVehicule : Vehicule , IVehicule
{
   private 
string  nom = "";
   private 
string [ ] ArrayInfos = new  string [10] ;
   public  event 
Starting OnStart ;
   protected void 
LancerEvent ( )   {
      if(
OnStart  !=  null)   OnStart ();
   }
   public 
string  this  [ int  index]    // implantation Indexeur
  
{
      
get  {   return  ArrayInfos[index] ;  }
      
set  {   ArrayInfos[index] = value ;   }
   }
   public 
string  TypeEngin    // implantation propriété
  
{
      
get  {  return  nom ;  }
      
set  {  nom = value ;  }
   }
   public virtual void 
Stopper ( )   {  } // implantation de méthode avec corps vide
}


Analyse :
Le qualificateur virtual indique que l'élément qualifié est virtuel, donc à liaison dynamique; sans autre qualification un élément est par défaut à liaison statique  :
  • La méthode public virtual void  Stopper ( ) est à liaison dynamique.
  • L'événement public event  Starting OnStart est à liaison statique.
  • La propriété public string  TypeEngin est à liaison statique.
  • L'indexeur  public  string  this  [ int  index] est à liaison statique.
  • La méthode  protected void  LancerEvent ( ) est à liaison statique.



2.4 Le code de la classe Terrestre

abstract class  Terrestre  UnVehicule  
{
   public new void 
RépartirPassagers ( )    {
      
//...implantation de méthode
   }
   public new void 
PériodicitéMaintenance ( )  {
  
  //...implantation de méthode
   }
}


Analyse :
Dans la classe mère Vehicule les deux méthodes RépartirPassagers et PériodicitéMaintenance sont à liaison statique, dans la classe Terrestre :
  • La méthode public new  void  RépartirPassagers ( )  est à laison statique et masque la méthode mère.
  • La méthode public new  void  PériodicitéMaintenance ( )  est à laison statique  et masque la méthode mère.



2.5 Le code de la classe Voiture

class  Voiture  Terrestre  
{
   public override void  
Demarrer ( )  {
      
LancerEvent ( );
   }
   public override void 
Stopper ( ) {
      
//...
   
}
 }


Analyse :
La méthode Demarrer( ) est héritée de la classe mère Vehicule, la méthode Stopper( ) est héritée de la classe UnVehicule :
  • La méthode public override void   Demarrer ( )  est à laison dynamique et redéfinit la méthode abstraite mère.
  • La méthode public override void  Stopper ( )  est à laison dynamique et redéfinit la méthode virtuelle à corps vide de la classe UnVehicule.



3. Cohérence de C# entre les notions de classe et d'interface


Une classe peut implémenter plusieurs interfaces. Dans ce cas nous avons une excellente alternative à l'héritage multiple.

Lorsque l'on crée une interface, on fournit un ensemble de définitions et de comportements qui ne devraient plus être modifiés. Cette attitude de constance dans les définitions, protège les applications écrites pour utiliser cette interface.

Les variables de types interface respectent les mêmes règles de transtypage que les variables de types classe. 

Les objets de type classe clA peuvent être transtypés et reférencés par des variables d'interface IntfA dans la mesure où la classe clA implémente l’interface IntfA. (cf. polymorphisme d'objet)
 



Une classe peut implémenter plusieures interfaces


Soit la définition suivante où la classe UnVehiculeMoteur hérite de la classe abstraite Vehicule et implémente l'interface IVehicule de l'exemple précédent, et supposons qu'en plus cette classe implémente l'interface IMoteur. L'interface IMoteur explique que lorsqu'un véhicule est à moteur, il faut se préoccuper de son type de carburant et de sa consommation :

 

Ci-dessous le code C# correspondant à cette définition, plus une classe concrète Voiture instanciable dérivant de la classe abstraite UnVehiculeMoteur :

abstract class  Vehicule   {
   public abstract void 
Demarrer ( ); // méthode abstraite
   public void  RépartirPassagers ( )  {  } // implantation de méthode avec corps vide
   public void  PériodicitéMaintenance ( ) {  } // implantation de méthode avec corps vide
}

interface  IVehicule {
   event 
Starting OnStart ;   // déclaration d'événement du type délégué : Starting
    string  this  [ int  index]    // déclaration d'indexeur
   
{       get ;       set ;   }
   
string  TypeEngin    // déclaration de propriété
   
{       get ;       set ;   }
   void 
Stopper ( ); // déclaration de méthode
}

enum Energie { gaz , fuel , ess } // type énuméré pour le carburant

interface IMoteur {
     Energie carburant // déclaration de propriété
      {       get ;        }
     int consommation ( ); // déclaration de méthode
}

abstract class  UnVehiculeMoteur : Vehicule , IVehicule , IMoteur
{
   private 
string  nom = "";
   
private  Energie  typeEnerg = Energie.fuel  ;
   private 
string [ ] ArrayInfos = new  string [10] ;
   public  event 
Starting OnStart ;
   protected void 
LancerEvent ( )   {
      if(
OnStart  !=  null)   OnStart ( );
   }
   public 
string  this  [ int  index]    // implantation Indexeur de IVehicule
  
{
      
get  {   return  ArrayInfos[index] ;  }
      
set  {   ArrayInfos[index] = value ;   }
   }
   public 
string  TypeEngin    // implantation propriété de IVehicule
  
{
      
get  {  return  nom ;  }
      
set  {  nom = value ;  }
   }

   public  Energie  carburant    // implantation propriété de IMoteur
   
{
      
get  {  return  typeEnerg ;  }
   }   
   public virtual void 
Stopper ( )   {  } // implantation vide de méthode  de IVehicule
   public virtual int  consommation ( )   { return .... } // implantation de méthode  de IMoteur
}

class  Voiture   UnVehiculeMoteur  {
   public override void  
Demarrer ( )  {
      
LancerEvent ( );
   }
   public override void 
Stopper ( ) {
      
//...implantation de méthode
   
}
  public new void  RépartirPassagers ( )    {
      
//...implantation de méthode
   }
   public new void 
PériodicitéMaintenance ( )  {
  
  //...implantation de méthode
   }
 }


Les interfaces et classes respectent les mêmes règles de polymorphisme

Il est tout à fait possible d'utiliser des variables de référence sur des interfaces et de les transtyper d'une manière identique à des variables de références de classe. En particulier le polymorphisme de référence s'applique aux références d'interfaces.

Le polymorphisme de référence sur les classes de l'exemple précédent

abstract class  Vehicule   { ... }
interface 
IVehicule { ... }
enum
Energie { gaz , fuel , ess }
interface
IMoteur { ... }

abstract class  UnVehiculeMoteur : Vehicule , IVehicule , IMoteur { ... }
class 
Voiture : UnVehiculeMoteur  ... }

class UseVoiture1 {
   public string use ( IVehicule x ){
        return x.TypeEngin ;
   }
}
class UseVoiture2 {
   public string use ( UnVehiculeMoteur x ){
        return x.TypeEngin ;
   }
}
class UseVoiture3 {
   public string use ( Voiture x ){
        return x.TypeEngin ;
   }
}



class  MaClass    {

 static void Main(string [ ] args)  {
  const string ch; s = "le client utilise une ";
  string ch ;
  IVehicule a1;
  UnVehiculeMoteur b1;
  Voiture c1;
  a1 = new Voiture( );
  a1.TypeEngin = "Renault";
  b1= new Voiture( );
  b1.TypeEngin = "Citroën";
  c1 = new Voiture( );
  c1.TypeEngin = "Peugeot";
  UseVoiture1 client = new UseVoiture1( );
  ch = s+client.use(a1);
  System.Console.WriteLine(ch);
  ch = s+client.use(b1);
  System.Console.WriteLine(ch);
  ch = s+client.use(c1);
  System.Console.WriteLine(ch);

 }
}

code d'exécution avec 3 objets différents
static void Main(string [ ] args)  {
  .... idem
 UseVoiture1 client = new UseVoiture1( );
  ch = s+client.use(a1);
  System.Console.WriteLine(ch);
  ch = s+client.use(b1);
  System.Console.WriteLine(ch);
  ch = s+client.use(c1);
  System.Console.WriteLine(ch);

}

IVehicule
   |
__UnVehiculeMoteur
           |__Voiture


Après exécution :


a1 est une référence sur  IVehicule, b1 est une référence sur une interface fille de IVehicule, c1 est de classe Voiture implémentant IVehicule.

Donc chacun de ces trois genres de paramètre peut être passé par polymorphisme de référence d'objet à la méthode public string use ( IVehicule x )

static void Main(string [ ] args)  {
 .... idem
 UseVoiture2 client = new UseVoiture2( );
  ch = s+client.use((UnVehiculeMoteur)a1);
  System.Console.WriteLine(ch);
  ch = s+client.use(b1);
  System.Console.WriteLine(ch);
  ch = s+client.use(c1);
  System.Console.WriteLine(ch);

}

IVehicule
   |
__UnVehiculeMoteur
           |__Voiture


Après exécution :


Le polymorphisme de référence d'objet appliqué à la méthode public string use ( UnVehiculeMoteur x ) indique que les paramètres passé doivent être de type UnVehiculeMoteur ou de type descendant.

La variable a1 est une référence sur  IVehicule qui ne descend pas de UnVehiculeMoteur, il faut donc transtyper la référence a1 soit : (UnVehiculeMoteur)a1.
static void Main(string [ ] args)  {
 .... idem
 UseVoiture3 client = new UseVoiture3( );
  ch = s+client.use( (Voiture)a1 );
  System.Console.WriteLine(ch);
  ch = s+client.use(
(Voiture)b1 );
  System.Console.WriteLine(ch);
  ch = s+client.use(c1);
  System.Console.WriteLine(ch);

}

IVehicule
   |
__UnVehiculeMoteur
           |__Voiture


Après exécution :


Le polymorphisme de référence d'objet appliqué à la méthode public string use ( Voiture x ) indique que les paramètres passés doivent être de type Voiture ou de type descendant.

La variable a1 est une référence sur  IVehicule qui ne descend pas de Voiture, il faut donc transtyper la référence a1 soit : (Voiture)a1

La variable b1 est une référence sur  UnVehiculeMoteur qui ne descend pas de Voiture,  il faut donc transtyper la référence b1 soit : (Voiture)b1

Les opérateurs is et as sont utilisables avec des références d'interfaces en C#. Reprenons l'exemple précédent :

abstract class  Vehicule   { ... }
interface 
IVehicule { ... }
enum
Energie { gaz , fuel , ess }
interface
IMoteur { ... }
abstract class 
UnVehiculeMoteur : Vehicule , IVehicule , IMoteur { ... }
class 
Voiture : UnVehiculeMoteur  ... }

class UseVoiture1 {
   public string use ( IVehicule x ){
        if (x is UnVehiculeMoteur) {
          int
consom =   (x as UnVehiculeMoteur).consommation( );
          return " consommation="+consom.ToString( ) ;
       
       else

         return x.TypeEngin ;
   }
}


Les conflits de noms dans les interfaces

Il est possible que deux interfaces différentes possèdent des membres ayant la même signature. Une classe qui implémente ces deux interfaces se trouvera confrontée à un conflit de nom (ambiguïté). Le compilateur C# exige dès lors que l'ambiguïté soit levée avec le préfixage du nom du membre par celui de l'interface correspondante (implémentation explicite).

L'exemple ci-dessous est figuré avec deux interfaces IntA et IntB contenant  chacune deux méthodes portant les mêmes noms et plus particulièrement la méthode meth1 possède la même signature dans chaque interface. Soit ClasseUn une classe implémentant ces deux interfaces. Voici comment fait C# pour choisir les appels de méthodes implantées.

Le code source de ClasseUn  implémentant les deux interfaces IntA et IntB :
interface IntA{
 void meth1( int x );
 void meth2( );
}
interface IntB{
 void meth1( int x );
 void meth2( int y );
}
class ClasseUn :  IntA , IntB {
    public virtual void meth1( int x ){ }
    void
IntA.meth1( int x ){ }
    void IntB.meth1( int x ){ }
    public virtual void meth2( ){ }
    public void meth2( int y ){ }
}


Schéma expliquant ce que C# analyse dans le code source précédent :



Lorsque l'on instancie effectivement 3 objets de classe ClasseUn précédente, et que l'on déclare chaque objet avec un type de référence différent : classe ou interface, le schéma ci-dessous indique quels sont les différents appels de méthodes corrects possibles :



Il est aussi possible de transtyper une référence d'objet de classe ClasseUn en une référence d'interface dont elle hérite (les appels sont identiques à ceux du schéma précédent) :

class Tests    {
        void methTest( ) {
            ClasseUn Obj1 = new ClasseUn( );
            IntA Obj2 = ( IntA )Obj1;
            IntB Obj3 = ( IntB )Obj1;
            Obj1.meth2( );
            Obj1.meth2(74);
            Obj2.meth2( );
            Obj3.meth2(100);
            Obj1.meth1(50);
            Obj2.meth1(40);
            Obj3.meth1(40);
        }
    }
Nous remarquons qu'aucun conflit et aucune ambiguïté de méthode ne sont possibles et que grâce à l'implémentation explicite toutes les méthodes de même nom sont accessibles.

Enfin, nous avons préféré utiliser le transtypage détaillé dans :
IntA Obj2 = ( IntA )Obj1 ;
Obj2.meth1(40) ;
plutôt que l'écriture équivalente :
(( IntA )Obj1).meth1(40) ; //...appel de IntA.meth1(int x)

car l'oubli du parenthésage externe dans l'instruction " (( IntA )Obj1).meth1(40) "  peut provoquer des incompréhensions dans la mesure où aucune erreur n'est signalé par le compilateur car ce n'est plus la même méthode qui est appelée.
( IntA )Obj1.meth1(40) ; //...appel de public virtual ClasseUn.meth1(int x)