CodeGym /Cours /C# SELF /Implémentation explicite d'une interface en C#

Implémentation explicite d'une interface en C#

C# SELF
Niveau 23 , Leçon 4
Disponible

1. Intro

D'habitude, quand tu implémentes une interface, tu crées juste des méthodes publiques dans ta classe, dont les signatures correspondent à celles déclarées dans l'interface. On appelle ça une implémentation implicite ou publique. Le compilateur est assez malin pour piger : "Ok, cette méthode DoSomething() dans la classe MyClass est là pour l'interface IDoable.DoSomething() !".

Mais si jamais :

  • Ta classe SmartDevice implémente l'interface ICamera (qui a une méthode TakePicture()) et l'interface IScreen (qui a aussi une méthode TakePicture() — pour faire une capture d'écran) ?
  • Ou bien ta classe Robot a déjà une méthode publique Reset() pour tout réinitialiser, mais tu veux aussi qu'elle implémente l'interface IDevice avec une méthode Reset() qui ne remet à zéro qu'une partie des réglages ?

Dans ces cas-là, il y a ambiguïté ou tu veux séparer clairement les fonctionnalités. C'est là que l'implémentation explicite d'interface débarque.

L'implémentation explicite d'interface te permet de dire au compilateur : "Cette méthode-là, elle est pour cette interface, et elle sera accessible uniquement via une référence de ce type d'interface". C'est comme Peter Parker qui lance sa toile seulement quand il agit en Spider-Man, et qui fait des photos quand il est juste "Peter Parker normal".

2. Quand et pourquoi utiliser l'implémentation explicite

Quand tu implémentes une interface de façon classique, ses méthodes et propriétés deviennent partie de l'API publique de ta classe. Mais parfois, tu veux qu'elles soient accessibles uniquement via l'interface, pas directement via la classe. Ça arrive plus souvent qu'on ne le pense ! Par exemple, si deux interfaces demandent la même méthode (mais avec un sens différent), ou si tu veux limiter l'accès à l'implémentation pour que seuls ceux qui bossent avec l'objet via l'interface y aient accès.

C'est là que l'implémentation explicite d'interface sauve la mise. C'est comme un passage secret dans l'archi du code : de l'extérieur, on ne le voit pas, mais ceux qui savent — ils passent !

Scénario 1 : Conflit de noms

Imagine, t'as une classe qui implémente deux interfaces, et les deux veulent une méthode avec le même nom, mais une logique totalement différente. Par exemple :

interface IWriter
{
    void Print();
}

interface IPrinter
{
    void Print();
}

Tu veux que IWriter.Print() écrive dans un fichier, et que IPrinter.Print() envoie sur l'imprimante. Une implémentation classique de Print() ne te permet pas de séparer les comportements. C'est là que l'implémentation explicite te sauve.

Scénario 2 : Cacher des méthodes techniques/pas utiles

Parfois, ta classe doit implémenter une méthode d'interface, mais tu ne veux surtout pas la proposer à tous les utilisateurs de la classe (genre, c'est juste pour l'infra interne).

Scénario 3 : Protection contre les appels "par erreur"

Si une méthode d'interface n'est pas faite pour être appelée directement (genre, un mécanisme interne du framework), tu peux l'implémenter explicitement — comme ça, un dev ne pourra pas l'appeler par accident via l'objet de la classe.

3. Syntaxe de l'implémentation explicite

La différence principale : en implémentation explicite, tu nommes les méthodes et propriétés avec le nom complet de l'interface. Et pas de modificateur d'accès (public/private) ni de mot-clé override !

Syntaxe générale :


Type_de_retour NomInterface.NomMethode(paramètres)
{
    // implémentation
}
Syntaxe de l'implémentation explicite d'interface

Ça donne l'impression que tu précises bien le nom de l'interface, pour que le compilateur et tes collègues ne se plantent pas sur le contrat concerné.

Résolution de conflit de noms

Regardons le cas avec IWriter et IPrinter. On continue notre appli d'apprentissage, où, par exemple, on a une classe de rapports :

interface IWriter
{
    void Print();
}

interface IPrinter
{
    void Print();
}

public class Report : IWriter, IPrinter
{
    // Implémentation explicite de IWriter.Print
    void IWriter.Print()
    {
        Console.WriteLine("On sauvegarde le rapport dans un fichier (Writer)...");
    }

    // Implémentation explicite de IPrinter.Print
    void IPrinter.Print()
    {
        Console.WriteLine("On envoie le rapport à l'imprimante papier (Printer)...");
    }

    // Méthode supplémentaire pour affichage utilisateur
    public void Show()
    {
        Console.WriteLine("On affiche le rapport à l'écran.");
    }
}

Essayons d'utiliser les différentes interfaces :

var report = new Report();

report.Show(); // Méthode publique classique

// report.Print(); // Erreur ! Pas de méthode Print dans la classe Report

IWriter writer = report;
writer.Print(); // Appelle l'implémentation IWriter.Print()

IPrinter printer = report;
printer.Print(); // Appelle l'implémentation IPrinter.Print()

Ici, les méthodes Print ne sont pas accessibles via l'objet report lui-même, mais seulement via l'interface appropriée. Voilà le cœur de l'implémentation explicite : tu passes par le "port" interface pour accéder à la méthode.

Comment ça se passe en mémoire : illustration simple

En gros, l'implémentation explicite "cache" le membre d'interface dans la classe. Pour visualiser, imagine ce tableau :

Comment on appelle Ce qui est vraiment exécuté
report.Print()
Erreur de compilation — pas de telle méthode
((IWriter)report).Print()
Implémentation explicite
IWriter.Print()
((IPrinter)report).Print()
Implémentation explicite
IPrinter.Print()
report.Show()
Méthode Show de la classe

4. Exemple : Interface — juste pour l'infra

Dans un vrai projet, on croise souvent ce scénario : une classe doit implémenter une interface technique, mais l'implémentation ne doit pas être visible pour les utilisateurs classiques de la classe.

interface IBroadcastable
{
    void Broadcast();
}

public class SecretMessage : IBroadcastable
{
    void IBroadcastable.Broadcast()
    {
        Console.WriteLine("Le message secret est parti dans l'éther...");
    }

    public void Reveal()
    {
        Console.WriteLine("On montre le secret à l'écran.");
    }
}

// Dans le code classique :
var message = new SecretMessage();
message.Reveal();  // Méthode utilisateur

// message.Broadcast(); // Erreur ! — pas de telle méthode

// Seule l'infra sait quoi faire :
((IBroadcastable)message).Broadcast();

Ici, la méthode Broadcast() — c'est juste pour ceux qui bossent via le contrat d'interface.

5. Implémentation explicite de propriétés et indexeurs

On peut aussi implémenter explicitement des propriétés, et même des indexeurs.

interface IDescribable
{
    string Description { get; }
}

public class Product : IDescribable
{
    // Implémentation explicite de la propriété
    string IDescribable.Description => "Description dispo uniquement via l'interface";

    // Propriété publique classique
    public string Name { get; set; }
}

// Exemple d'utilisation :
var p = new Product { Name = "Gadget" };
// p.Description; // Erreur ! Pas de telle propriété dans Product

var descr = ((IDescribable)p).Description;
Console.WriteLine(descr);

6. Petites subtilités utiles

Comment marche l'héritage avec l'implémentation explicite

Avec l'héritage, c'est assez intuitif : si la classe de base implémente explicitement une interface, la classe dérivée hérite de ce "comportement". Mais si la classe dérivée veut redéfinir l'implémentation de la méthode d'interface, c'est mort : une implémentation explicite ne peut pas être virtuelle. Donc, pas de redéfinition possible.

Si tu veux ce genre de comportement — il faut utiliser une implémentation classique ou regarder du côté du pattern "template method".

Implémentation explicite et implicite


+----------------+
|   Invoice      |
+----------------+
| Show()         |   // Appel direct possible
+----------------+
| ITxtExportable.Export()   // Uniquement via ITxtExportable
| IJsonExportable.Export()  // Uniquement via IJsonExportable
+----------------+

Particularités/limitations de l'implémentation explicite

  • Les méthodes et propriétés explicitement implémentées ne peuvent pas avoir de modificateur d'accès. Elles sont privées pour l'extérieur et accessibles uniquement via l'interface.
  • On ne peut pas les rendre static, virtual, abstract ou override.
  • Impossible d'accéder à un membre explicitement implémenté via l'objet de la classe directement (seulement via une variable de type interface).
  • Si une interface hérite d'une autre, on peut implémenter explicitement les membres de n'importe quel niveau de la hiérarchie.

7. Avantages de l'implémentation explicite

L'implémentation explicite, ce n'est pas juste un hack de syntaxe pour gérer les collisions. Il y a plusieurs bonnes raisons de l'utiliser :

Résolution des collisions de noms (The Big One) : C'est la raison principale et la plus évidente. Si deux interfaces que tu implémentes déclarent des méthodes (ou propriétés) avec la même signature, l'implémentation explicite te permet de fournir des implémentations séparées, spécifiques à chaque interface. Sinon, tu aurais une ambiguïté.
Exemple concret : Imagine que tu as une imprimante qui fait aussi scanner. L'interface IPrinter a une méthode Print(), et l'interface IScanner — une méthode Scan(). Mais si les deux avaient une méthode ProcessDocument() ? L'implémentation explicite te permet de faire IPrinter.ProcessDocument() pour l'impression et IScanner.ProcessDocument() pour le scan, et elles fonctionneront différemment.

Cacher les détails d'implémentation et garder l'API clean : Les méthodes explicitement implémentées ne font pas partie de l'API publique de ta classe. Elles sont accessibles seulement si tu castes l'objet en interface. Super utile si tu veux que certaines fonctionnalités ne soient dispo que "par contrat", pas comme comportement général de l'objet.
Exemple : Tu crées un outil financier complexe, genre CreditCard. Il peut implémenter IPayable (pour payer) et IAdminConfigurable (pour les réglages internes, genre fixer des limites). La méthode IAdminConfigurable.SetLimit() ne doit pas être accessible à tout le monde, juste à l'admin qui bosse avec CreditCard comme IAdminConfigurable. L'implémentation explicite permet de garder SetLimit() cachée, rendant l'API de la classe plus clean et plus safe.

Garantie du contrat : Parfois, une méthode de ta classe a par hasard la même signature qu'une méthode d'interface, mais tu ne veux pas qu'elle soit considérée comme son implémentation. Par exemple, t'as une classe MyList avec une méthode Clear() pour vider son état interne. Si tu décides que MyList doit implémenter IList<T>, qui a aussi Clear(), alors par défaut ton MyList.Clear() devient l'implémentation de IList<T>.Clear(). Si la logique doit être différente, l'implémentation explicite IList<T>.Clear() te permet de les séparer.

Implémentation implicite vs explicite

Pour bien piger la différence, regarde ce petit tableau comparatif :

Caractéristique Implémentation implicite Implémentation explicite
Accessibilité Accessible via la classe et via l'interface. Accessible uniquement via l'interface.
Modificateur d'accès En général public (ou protected, etc.). Pas de modificateur d'accès (ne peut pas être public).
Syntaxe
public ReturnType MethodName(Params) { ... }
ReturnType InterfaceName.MethodName(Params) { ... }
Gestion des collisions Ne gère pas, la méthode sert à toutes les interfaces avec cette signature. Gère les collisions, permet des implémentations différentes.
Nettoyage de l'API de la classe La méthode fait partie de l'API publique de la classe. La méthode ne fait pas partie de l'API publique de la classe.
Usage Pour les implémentations d'interface simples et claires. Pour gérer les collisions ou cacher de la logique spécifique.

Tu vois, c'est deux faces d'une même pièce, chacune a son utilité. La plupart du temps tu feras de l'implémentation implicite, c'est plus simple et pratique. L'implémentation explicite, c'est l'outil pour les cas spéciaux mais importants.

8. Erreurs classiques avec l'implémentation explicite

Erreur n°1 : la méthode n'est pas visible via l'objet de la classe.
Les méthodes explicitement implémentées ne sont pas accessibles directement via l'instance de la classe. Souvent, ça embrouille : le compilateur dit que la méthode n'existe pas, alors qu'elle est juste "cachée". Solution : caste l'objet en type interface, genre : (IMyInterface)obj.Method().

Erreur n°2 : mauvais usage des modificateurs d'accès.
En implémentation explicite, pas de modificateurs comme public ou override. Si tu tentes, tu auras une erreur de compilation — le compilateur n'accepte pas ça.

Erreur n°3 : tentative de redéfinir une méthode explicitement implémentée dans une classe dérivée.
Si une méthode d'interface est explicitement implémentée dans la classe de base, tu ne peux pas la redéfinir dans la classe fille. Faut garder ça en tête quand tu conçois une hiérarchie de classes avec des interfaces.

1
Étude/Quiz
Notion d'interface, niveau 23, leçon 4
Indisponible
Notion d'interface
Interfaces : bases et contrats
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION