par , 03/08/2018 à 16h45 (4162 Affichages)
Le billet du jour va nous permettre de découvrir et/ou d'approfondir un aspect souvent méconnu de C# : l'implémentation des interfaces. En effet, lors qu'une classe implémente une méthode d'une interface, elle peut le faire de deux manières différentes :
- soit de manière implicite (la méthode "classique") ;
- soit de manière explicite (la méthode "méconnue").
Outre les syntaxes différentes, dans ce billet, nous allons voir exactement les différences entre les deux, et un cas pratique de l'utilité de ces deux syntaxes.
Exemple d'interface : ICloneable
Tout au long de ce billet, nous allons utiliser l'interface ICloneable pour illustrer les propos, mais bien entendu, cela est généralisable à toutes les interfaces.
Cette interface est définie comme suit :
1 2 3 4
| public interface ICloneable
{
object Clone();
} |
Exemple de classe : MonObjet
Maintenant, imaginons que nous ayons la classe suivante :
1 2 3 4 5
| public class MonObjet
{
public int ID {get;set;}
public string Nom {get;set;}
} |
Implémentation implicite
Nous souhaitons maintenant pouvoir dupliquer cet objet facilement. Une des méthodes utilisables est de créer un constructeur prenant en paramètre une instance de MonObjet, et qui permet d'initialiser la nouvelle instance à partir de l'instance passée en paramètre.
Une autre méthode est d'implémenter l'interface IClonable afin d'ajouter la méthode Clone() qui permettra de réaliser cette copie. Utilisons donc cette dernière méthode afin d'illustrer ce billet.
Faisons-le en utilisant l'implémentation implicite. Cela donne :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MonObjet : ICloneable
{
public int ID {get;set;}
public string Nom {get;set;}
public object Clone()
{
MonObject clone = new MonObjet()
{
ID = this.ID,
Nom = this.Nom
};
return clone;
}
} |
Voilà, rien de plus simple pour cet exemple. Maintenant, l'oeil averti remarque que la méthode Clone() renvoie une instance de type Object et non pas du type MonObjet. Aussi, si on veut utiliser la nouvelle instance il est nécessaire de la caster (par exemple, avec le mot-clé as).
On pourrait se dire que c'est un peu dommage. Ne serait-il pas possible de réaliser la même chose, mais en ayant le bon type en retour ?
De la modification du type de retour...
Si on essaie de le faire directement, c'est-à-dire ainsi :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MonObjet : ICloneable
{
public int ID {get;set;}
public string Nom {get;set;}
public MonObjet Clone()
{
MonObject clone = new MonObjet()
{
ID = this.ID,
Nom = this.Nom
};
return clone;
}
} |
Alors le compilateur va générer une erreur, précisant que la classe MonObjet n'implémente pas la méthode Clone() de l'interface ICloneable car le type de retour ne correspond pas à celui de l'interface.
Impossible non plus de surcharger la méthode :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class MonObjet : ICloneable
{
public int ID {get;set;}
public string Nom {get;set;}
public object Clone()
{
MonObject clone = new MonObjet()
{
ID = this.ID,
Nom = this.Nom
};
return clone;
}
public MonObjet Clone()
{
MonObject clone = new MonObjet()
{
ID = this.ID,
Nom = this.Nom
};
return clone;
}
} |
car les deux méthodes ne différeraient alors que par le type de retour. Or, le type de retour ne fait pas partie de la signature d'une méthode, et cela reviendrait donc à redéfinir la même méthode deux fois.
...à l'implémentation explicite
C'est ici que va intervenir l'implémentation explicite d'une méthode d'une interface. Si nous écrivons :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public class MonObjet : ICloneable
{
public int ID {get;set;}
public string Nom {get;set;}
object ICloneable.Clone()
{
MonObject clone = new MonObjet()
{
ID = this.ID,
Nom = this.Nom
};
return clone;
}
public MonObjet Clone()
{
MonObject clone = new MonObjet()
{
ID = this.ID,
Nom = this.Nom
};
return clone;
}
} |
Le compilateur ne se plaint pas. Il accepte notre code. Si nous regardons bien la première méthode, on peut constater :
- qu'elle ne précise pas la visibilité de la méthode (pas de public ou internal) ;
- que le nom de la méthode est précédé par le nom de l'interface (ICloneable.Clone), d'où le nom d'explicite.
Qu'est-ce que cela signifie ? En fait, lorsqu'une méthode d'une interface est implémentée de manière explicite, cette méthode ne sera appelable que si la variable contenant l'objet est du type de l'interface. Si la variable est du type MonObjet, la méthode implémentée explicitement ne sera pas visible. Ainsi, si on a une variable de type MonObjet, l'appel à la méthode Clone() ne peut faire référence qu'à la méthode retournant un objet de type MonObjet :
1 2 3 4 5 6
| MonObjet monObjet = new MonObjet();
ICloneable clonable = monObjet;
MonObjet clone;
clone = monObjet.Clone(); // OK, la méthode Clone de la classe est appelée car la méthode issue de l'interface est masquée
clone = clonable.Clone(); // Erreur de compilation ! La méthode Clone de l'interface retourne une instance de Object et non une instance de MonObjet |
Si, par contre, on manipule l'objet via une variable de type ICloneable, alors la seule méthode Clone() disponible sera celle de l'interface.
Dans tous les cas, il n'y a donc plus d'ambiguïté !
Revenons sur l'absence d'indicateur de visibilité pour la méthode dans le cas d'une implémentation explicite. Il n'y en a pas besoin, car c'est... inutile ! Du fait que cette méthode n'est appelable que via une variable du type de l'interface, la méthode est forcément visible. C'est pourquoi il est inutile (et même impossible !) de préciser la visibilité.
Le mot de la fin
Il faut bien avoir en tête que le choix de l'implémentation implicite/explicite d'une interface se fait au niveau des méthodes, et non au niveau de l'interface elle-même. Cela implique donc qu'il est tout à fait possible d'implémenter certaines méthodes de manière implicite, et d'autres de manière explicite !
Nous verrons dans d'autres billets d'autre type d'usage des interfaces explicites.