Pourquoi implémenter l'interface explicitement?

122

Alors, quel est exactement un bon cas d'utilisation pour implémenter une interface explicitement?

Est-ce uniquement pour que les utilisateurs de la classe n'aient pas à regarder toutes ces méthodes / propriétés dans Intellisense?

Maître Moralité
la source

Réponses:

146

Si vous implémentez deux interfaces, toutes deux avec la même méthode et des implémentations différentes, vous devez implémenter explicitement.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}
Iain
la source
Oui, c'est exactement un cas que EIMI résout. Et d'autres points sont couverts par la réponse «Michael B».
crypté
10
Excellent exemple. J'adore les noms d'interface / classe! :-)
Brian Rogers
11
Je n'aime pas ça, deux méthodes avec la même signature dans une classe qui font des choses très différentes? Ceci est extrêmement dangereux et va très probablement causer des ravages dans tout grand développement. Si vous avez un code comme celui-ci, je dirais que votre analyse et votre conception sont à la hauteur.
Mick
4
@Mike Les interfaces peuvent appartenir à une API ou à deux API différentes. Peut-être que l' amour est un peu exagéré ici, mais je serais au moins heureux qu'une implémentation explicite soit disponible.
TobiMcNamobi
@BrianRogers Et les noms de méthodes aussi ;-)
Sнаđошƒаӽ
66

Il est utile de masquer le membre non préféré. Par exemple, si vous implémentez les deux IComparable<T>et IComparablequ'il est généralement plus agréable de masquer la IComparablesurcharge pour ne pas donner aux gens l'impression que vous pouvez comparer des objets de types différents. De même, certaines interfaces ne sont pas compatibles CLS, par exemple IConvertible, si vous n'implémentez pas explicitement l'interface, les utilisateurs finaux de langages qui nécessitent la conformité CLS ne peuvent pas utiliser votre objet. (Ce qui serait très désastreux si les implémenteurs BCL ne cachaient pas les membres IConvertible des primitives :))

Une autre note intéressante est que normalement l'utilisation d'une telle construction signifie que la structure qui implémente explicitement une interface ne peut les invoquer qu'en encadrant le type d'interface. Vous pouvez contourner cela en utilisant des contraintes génériques:

void SomeMethod<T>(T obj) where T:IConvertible

Ne mettra pas en boîte un int lorsque vous lui en passerez un.

Michael B
la source
1
Vous avez une faute de frappe dans votre contrainte. Pour clarifier, le code ci-dessus fonctionne. Il doit figurer dans la déclaration initiale de la signature de méthode dans l'interface. Le message d'origine ne le précisait pas. De plus, le format approprié est "void SomeMehtod <T> (T obj) où T: IConvertible. Notez qu'il y a un deux-points supplémentaire entre") "et" where "qui ne devrait pas être là. Encore, +1 pour une utilisation intelligente de génériques pour éviter la boxe coûteuse.
Zack Jannsen
1
Salut Michael B. Alors pourquoi dans l'implémentation de la chaîne dans .NET il y a l'implémentation publique de IComparable: public int CompareTo (Object value) {if (value == null) {return 1; } if (! (value is String)) {throw new ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } return String.Compare (this, (String) value, StringComparison.CurrentCulture); } Merci!
zzfima
stringétait là avant les génériques et cette pratique était en vogue. Lorsque .net 2 est arrivé, ils ne voulaient pas casser l'interface publique du, stringalors ils l'ont laissé tel quel avec la sauvegarde en place.
Michael B
37

Quelques raisons supplémentaires pour implémenter une interface explicitement:

rétrocompatibilité : en cas de ICloneablemodification de l' interface, les membres de la classe de méthode d'implémentation n'ont pas à changer leurs signatures de méthode.

code plus propre : il y aura une erreur du compilateur si la Cloneméthode est supprimée de ICloneable, cependant si vous implémentez la méthode implicitement, vous pouvez vous retrouver avec des méthodes publiques `` orphelines '' inutilisées

typage fort : pour illustrer l'histoire de supercat avec un exemple, ce serait mon exemple de code préféré, l'implémentation ICloneablepermet explicitement Clone()d'être fortement typé lorsque vous l'appelez directement en tant que MyObjectmembre d'instance:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}
Wiebe Tijsma
la source
Pour celui-là, je préférerais interface ICloneable<out T> { T Clone(); T self {get;} }. Notez qu'il n'y a délibérément aucune ICloneable<T>contrainte sur T. Alors qu'un objet ne peut généralement être cloné en toute sécurité que si sa base le peut, on peut souhaiter dériver d'une classe de base qui pourrait être clonée en toute sécurité un objet d'une classe qui ne le peut pas. Pour permettre cela, je recommande de ne pas laisser les classes héritables exposer une méthode de clone public. Au lieu de cela, ont des classes héritables avec une protectedméthode de clonage et des classes scellées qui en dérivent et exposent le clonage public.
supercat
Bien sûr, ce serait plus agréable, sauf qu'il n'y a pas de version covariante d'ICloneable dans la BCL, vous devez donc en créer une, non?
Wiebe Tijsma
Les 3 exemples dépendent de situations improbables et des meilleures pratiques d'interface.
MickyD
13

Une autre technique utile consiste à demander à l'implémentation publique d'une fonction d'une méthode de renvoyer une valeur plus spécifique que celle spécifiée dans une interface.

Par exemple, un objet peut implémenter ICloneable, mais sa Cloneméthode visible publiquement renvoie son propre type.

De même, an IAutomobileFactorypeut avoir une Manufactureméthode qui retourne an Automobile, mais a FordExplorerFactory, qui implémente IAutomobileFactory, peut avoir sa Manufactureméthode retourner a FordExplorer(qui dérive de Automobile). Le code qui sait qu'il a un FordExplorerFactorypourrait utiliser des FordExplorerpropriétés spécifiques sur un objet retourné par un FordExplorerFactorysans avoir à faire de transtypage, tandis que le code qui savait simplement qu'il avait un type de IAutomobileFactorytraiterait simplement son retour comme un Automobile.

supercat
la source
2
+1 ... ce serait mon utilisation préférée des implémentations d'interface explicites, même si un petit échantillon de code serait probablement un peu plus clair que cette histoire :)
Wiebe Tijsma
7

C'est également utile lorsque vous avez deux interfaces avec le même nom de membre et la même signature, mais que vous souhaitez en modifier le comportement en fonction de son utilisation. (Je ne recommande pas d'écrire un code comme celui-ci):

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}
vcsjones
la source
60
par exemple associés plus étrange OO que j'ai jamais vu:public class Animal : Cat, Dog
MBX
38
@mbx: Si Animal implémentait également Parrot, ce serait un animal à morphing Polly.
RenniePet
3
Je me souviens d'un personnage de dessin animé qui était un chat à une extrémité et un chien à l'autre extrémité ;-)
George Birbilis
2
il y avait une série télévisée dans les années 80 .. "Manimal" .. où un homme pouvait se transformer en .. oh tant pis
bkwdesign
Les morkies ressemblent un peu à des chats, et sont aussi des chats ninja.
samis
6

Il peut garder l'interface publique plus propre pour implémenter explicitement une interface, c'est-à-dire que votre Fileclasse peut implémenter IDisposableexplicitement et fournir une méthode publique Close()qui pourrait avoir plus de sens pour un consommateur que Dispose().

F # propose uniquement une implémentation d'interface explicite, vous devez donc toujours effectuer un cast vers l'interface particulière pour accéder à ses fonctionnalités, ce qui permet une utilisation très explicite (sans jeu de mots) de l'interface.

Verre brisé
la source
Je pense que la plupart des versions de VB ne supportaient également que les définitions d'interface explicites.
Gabe
1
@Gabe - c'est plus subtil que pour VB - la dénomination et l'accessibilité des membres qui implémentent une interface sont distinctes de l'indication qu'ils font partie de l'implémentation. Ainsi, dans VB, et en regardant la réponse de @ Iain (réponse actuelle principale), vous pouvez implémenter IDoItFast et IDoItSlow avec les membres publics "GoFast" et "GoSlow", respectivement.
Damien_The_Unbeliever
2
Je n'aime pas votre exemple particulier (à mon humble avis, les seules choses qui devraient se cacher Disposesont celles qui ne nécessiteront jamais de nettoyage); un meilleur exemple serait quelque chose comme l'implémentation d'une collection immuable de IList<T>.Add.
supercat
5

Si vous avez une interface interne et que vous ne souhaitez pas implémenter publiquement les membres de votre classe, vous les implémenterez explicitement. Les implémentations implicites doivent être publiques.

Mike Dour
la source
Ok, cela explique pourquoi le projet ne se compilerait pas avec une implémentation implicite.
pauloya
4

Une autre raison de l'implémentation explicite est la maintenabilité .

Quand une classe est "occupée" - oui, cela arrive, nous n'avons pas tous le luxe de refactoriser le code des autres membres de l'équipe - alors avoir une implémentation explicite indique clairement qu'une méthode est là pour satisfaire un contrat d'interface.

Cela améliore donc la "lisibilité" du code.

h bob
la source
À mon humble avis, il est plus important de décider si la classe doit ou non exposer la méthode à ses clients. Cela conduit à être explicite ou implicite. Documenter que plusieurs méthodes vont de pair, dans ce cas parce qu'elles satisfont à un contrat - c'est #regionpour cela, avec une chaîne de titre appropriée. Et un commentaire sur la méthode.
ToolmakerSteve
1

Un autre exemple est donné par System.Collections.Immutable , dans lequel les auteurs ont choisi d'utiliser la technique pour conserver une API familière pour les types de collection tout en supprimant les parties de l'interface qui n'ont aucune signification pour leurs nouveaux types.

Concrètement, ImmutableList<T>implémente IList<T>et donc ICollection<T>( afin de permettre ImmutableList<T>d'être utilisé plus facilement avec du code hérité), n'a pourtant void ICollection<T>.Add(T item)aucun sens pour un ImmutableList<T>: puisque l'ajout d'un élément à une liste immuable ne doit pas changer la liste existante, ImmutableList<T>dérive également de IImmutableList<T>dont IImmutableList<T> Add(T item)peut être utilisé pour listes immuables.

Ainsi, dans le cas de Add, les implémentations ImmutableList<T>finissent par se présenter comme suit:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();
fuglede
la source
0

Dans le cas d'interfaces explicitement définies, toutes les méthodes sont automatiquement privées, vous ne pouvez pas leur donner un modificateur d'accès public. Supposer:

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object
Md Shahriar
la source
1
"toutes les méthodes sont automatiquement privées" - ce n'est pas techniquement correct, b / c si elles étaient en fait privées, elles ne seraient pas du tout appelables, casting ou non.
samis
0

Voici comment nous pouvons créer une interface explicite: si nous avons 2 interfaces et que les deux interfaces ont la même méthode et qu'une seule classe hérite de ces 2 interfaces, donc lorsque nous appelons une méthode d'interface, le compilateur a confondu la méthode à appeler, nous pouvons donc gérer ce problème à l'aide de l'interface explicite. Voici un exemple que j'ai donné ci-dessous.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
Debendra Dash
la source