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?
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?
Réponses:
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()
{
}
}
Il est utile de masquer le membre non préféré. Par exemple, si vous implémentez les deux IComparable<T>
et IComparable
qu'il est généralement plus agréable de masquer la IComparable
surcharge 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.
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, string
alors ils l'ont laissé tel quel avec la sauvegarde en place.
Quelques raisons supplémentaires pour implémenter une interface explicitement:
rétrocompatibilité : en cas de ICloneable
modification 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 Clone
mé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 ICloneable
permet explicitement Clone()
d'être fortement typé lorsque vous l'appelez directement en tant que MyObject
membre d'instance:
public class MyObject : ICloneable
{
public MyObject Clone()
{
// my cloning logic;
}
object ICloneable.Clone()
{
return this.Clone();
}
}
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 protected
méthode de clonage et des classes scellées qui en dérivent et exposent le clonage public.
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 Clone
méthode visible publiquement renvoie son propre type.
De même, an IAutomobileFactory
peut avoir une Manufacture
méthode qui retourne an Automobile
, mais a FordExplorerFactory
, qui implémente IAutomobileFactory
, peut avoir sa Manufacture
méthode retourner a FordExplorer
(qui dérive de Automobile
). Le code qui sait qu'il a un FordExplorerFactory
pourrait utiliser des FordExplorer
propriétés spécifiques sur un objet retourné par un FordExplorerFactory
sans avoir à faire de transtypage, tandis que le code qui savait simplement qu'il avait un type de IAutomobileFactory
traiterait simplement son retour comme un Automobile
.
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
}
public class Animal : Cat, Dog
Il peut garder l'interface publique plus propre pour implémenter explicitement une interface, c'est-à-dire que votre File
classe peut implémenter IDisposable
explicitement 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.
Dispose
sont 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
.
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.
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.
#region
pour cela, avec une chaîne de titre appropriée. Et un commentaire sur la méthode.
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();
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
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();
}
}
}