Rappels sur la notion d'interface
1.Concepts et vocabulaire d'interface en C#
- les interfaces peuvent constituer des hiérarchies et hériter entre elles
- la construction d'un objet necessite une classe implémentant l'interface
- les implémentations d'un membre d'interface sont en général public
- les implémentations explicites d'un membre d'interface sont spéciales
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
- une classe peut implémenter plusieures interfaces
- les interfaces et les classes respectent les mêmes règles de polymorphisme
- les conflits de noms dans les interfaces
Interface en C#
Rappels essentiels sur la notion d'interface
- Les interfaces ressemblent aux classes abstraites : elles contiennent des membres spécifiant certains comportements sans les implémenter.
- Les classes abstraites et les interfaces se différencient principalement par le fait qu'une classe peut implémenter un nombre quelconque d'interfaces, alors qu'une classe abstraite ne peut hériter que d'une seule classe abstraite ou non.
- Une interface peut servir à représenter des comportements d'héritage multiple.
Quelques conseils généraux prodigués par des développeurs professionnels (microsoft, Borland, Sun) :
- Les interfaces bien conçues sont plutôt petites et indépendantes les unes des autres.
- Un trop grand nombre de fonctions rend l'interface peu maniable.
- Si une modification s'avère nécessaire, une nouvelle interface doit être créée.
- Si la fonctionnalité que vous créez peut être utile à de nombreux objets différents, faites appel à une interface.
- Si vous créez des fonctionnalités sous la forme de petits morceaux concis, faites appel aux interfaces.
- L'utilisation d'interfaces permet d'envisager une conception qui sépare la manière d'utiliser une classe de la manière dont elle est implémentée.
- Deux classes peuvent partager la même interface sans descendre nécessairement de la même classe de base.
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 :
- Lorsque vous voulez qu'un membre (une méthode par exemple) implémenté d'une interface soit privé dans une classe pour toutes les instances de classes qui en dériveront, l'implémentation explicite vous permet de rendre ce membre (cette méthode) inaccessible à tout objet.
- Lors d'un conflit de noms si deux interfaces possèdent un membre ayant la même signature et que votre classe implémente les deux interfaces.
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 :
![]()
- La méthode Démarrer qui est abstraite,
- La méthode RépartirPassagers de répartition des passagers à bord du véhicule, implantée avec un corps vide,
- La méthode PériodicitéMaintenance renvoyant la périodicité de la maintenance obligatoire du véhicule, implantée avec un corps vide.
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 :
![]()
- L'événement OnStart est de type délégué (on construit un type délégué Starting( ) spécifique pour lui) et se déclenchera au démarrage du futur véhicule,
- L'indexeur this [int ] est de type string et permettra d'accéder à une liste indicée d'informations sur le futur véhicule,
- La propriété TypeEngin est en lecture et écriture et concerne le type du futur véhicule dans la marque,
- La méthode Stopper( ) indique comment le futur véhicule s'immobilisera.
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'exempleNous 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
}
![]()
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
}
![]()
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
}
![]()
abstract class Terrestre : UnVehicule
{
public new void RépartirPassagers ( ) {
//...implantation de méthode
}
public new void PériodicitéMaintenance ( ) {
//...implantation de méthode
}
}
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 {
enum Energie { gaz , fuel , ess } // type énuméré pour le carburant
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
}
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) {
UseVoiture1 client = new UseVoiture1( );
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";
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) {
ch = s+client.use(a1);
.... idem
UseVoiture1 client = new UseVoiture1( );
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) {
ch = s+client.use((UnVehiculeMoteur)a1);
.... idem
UseVoiture2 client = new UseVoiture2( );
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) {
ch = s+client.use( (Voiture)a1 );
.... idem
UseVoiture3 client = new UseVoiture3( );
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 { ... }
class UseVoiture1 {
interface IVehicule { ... }
enum Energie { gaz , fuel , ess }
interface IMoteur { ... }
abstract class UnVehiculeMoteur : Vehicule , IVehicule , IMoteur { ... }
class Voiture : UnVehiculeMoteur { ... }
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 :
plutôt que l'écriture équivalente :
IntA Obj2 = ( IntA )Obj1 ;
Obj2.meth1(40) ;
(( 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)