Quelles sont les différences dans la mise en œuvre d'interfaces implicitement et explicitement en C #?
Quand devriez-vous utiliser implicite et quand devriez-vous utiliser explicite?
Y a-t-il des avantages et/ou des inconvénients pour l'un ou l'autre?
Les directives officielles de Microsoft (à partir de la première édition Framework Design Guidelines ) stipulent que l'utilisation d'implémentations explicites n'est pas recommandée , car elle donne le code comportement inattendu.
Je pense que cette directive est très valable dans une période pré-IoC , lorsque vous ne transmettez pas des éléments comme interfaces.
Quelqu'un pourrait-il aborder cet aspect également?
Implicite est le moment où vous définissez votre interface via un membre de votre classe. Explicite est le moment où vous définissez des méthodes au sein de votre classe sur l'interface. Je sais que cela semble déroutant, mais voici ce que je veux dire: IList.CopyTo
serait implicitement implémenté comme:
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
et explicitement comme:
void ICollection.CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
La différence est qu'implicitement, il est accessible via votre classe que vous avez créée lorsqu'elle est convertie en tant que classe ainsi que lorsqu'elle est convertie en interface. L'implémentation explicite lui permet d'être accessible uniquement lorsqu'il est converti en interface elle-même.
MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.
J'utilise explicitement principalement pour garder l'implémentation propre ou quand j'ai besoin de deux implémentations. Mais peu importe, je l'utilise rarement.
Je suis sûr qu'il y a plus de raisons de l'utiliser/de ne pas l'utiliser que d'autres posteront.
Voir le prochain article dans ce fil pour un excellent raisonnement derrière chacun.
Une définition implicite consisterait simplement à ajouter les méthodes/propriétés, etc. requises par l'interface directement à la classe en tant que méthodes publiques.
La définition explicite force les membres à être exposés uniquement lorsque vous utilisez l'interface directement, et non l'implémentation sous-jacente. Ceci est préféré dans la plupart des cas.
En plus des excellentes réponses déjà fournies, il existe des cas où une implémentation explicite est OBLIGATOIRE pour que le compilateur puisse déterminer ce qui est requis. Jetez un coup d'œil à IEnumerable<T>
comme un excellent exemple qui apparaîtra assez souvent.
Voici un exemple:
public abstract class StringList : IEnumerable<string>
{
private string[] _list = new string[] {"foo", "bar", "baz"};
// ...
#region IEnumerable<string> Members
public IEnumerator<string> GetEnumerator()
{
foreach (string s in _list)
{ yield return s; }
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
Ici, IEnumerable<string>
implémente IEnumerable
, nous devons donc le faire aussi. Mais attendez, les versions générique et normale les deux implémentent des fonctions avec la même signature de méthode (C # ignore le type de retour pour cela). C'est complètement légal et bon. Comment le compilateur détermine-t-il lequel utiliser? Cela vous oblige à avoir, tout au plus, une définition implicite, ce qui permet de résoudre tous les problèmes.
c'est à dire.
StringList sl = new StringList();
// uses the implicit definition.
IEnumerator<string> enumerableString = sl.GetEnumerator();
// same as above, only a little more explicit.
IEnumerator<string> enumerableString2 = ((IEnumerable<string>)sl).GetEnumerator();
// returns the same as above, but via the explicit definition
IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();
PS: Le petit morceau d'indirection dans la définition explicite de IEnumerable fonctionne car, à l'intérieur de la fonction, le compilateur sait que le type réel de la variable est une liste de chaînes et que c'est ainsi qu'il résout l'appel de la fonction. Nifty petit fait pour implémenter certaines des couches d'abstraction que certaines des interfaces de base .NET semblent s'être accumulées.
J'ai tendance à utiliser une implémentation d'interface explicite lorsque je veux décourager "la programmation d'une implémentation" (Principes de conception à partir de modèles de conception) .
Par exemple, dans une application Web basée sur MVP -:
public interface INavigator {
void Redirect(string url);
}
public sealed class StandardNavigator : INavigator {
void INavigator.Redirect(string url) {
Response.Redirect(url);
}
}
Maintenant, une autre classe (telle que presenter ) dépend moins de l'implémentation de StandardNavigator et plus probablement de l'interface INavigator (car l'implémentation doit être convertie en une interface pour pouvoir utiliser la méthode de redirection).
Une autre raison pour laquelle je pourrais utiliser une implémentation d'interface explicite serait de garder le nettoyeur d'interface "par défaut" d'une classe. Par exemple, si je développais un contrôle de serveur ASP.NET , je voudrais peut-être deux interfaces:
Un exemple simple suit. C'est un contrôle de liste déroulante qui répertorie les clients. Dans cet exemple, le développeur de pages Web ne souhaite pas renseigner la liste. au lieu de cela, ils veulent simplement pouvoir sélectionner un client par GUID ou obtenir le GUID du client sélectionné. Un présentateur remplirait la zone lors du chargement de la première page et ce présentateur serait encapsulé par le contrôle.
public sealed class CustomerComboBox : ComboBox, ICustomerComboBox {
private readonly CustomerComboBoxPresenter presenter;
public CustomerComboBox() {
presenter = new CustomerComboBoxPresenter(this);
}
protected override void OnLoad() {
if (!Page.IsPostBack) presenter.HandleFirstLoad();
}
// Primary interface used by web page developers
public Guid ClientId {
get { return new Guid(SelectedItem.Value); }
set { SelectedItem.Value = value.ToString(); }
}
// "Hidden" interface used by presenter
IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}
Le présentateur remplit la source de données et le développeur de pages Web n'a jamais besoin de connaître son existence.
Je ne recommanderais pas toujours d'employer des implémentations d'interface explicites. Ce ne sont que deux exemples où ils pourraient être utiles.
Pour citer Jeffrey Richter de CLR via C #
(EIMI signifie E xplicit I nterface M ethod I mplementation)
Il est essentiel que vous compreniez certaines des ramifications inhérentes à l'utilisation des EIMI. Et à cause de ces ramifications, vous devriez essayer d'éviter autant que possible les EIMI. Heureusement, les interfaces génériques vous aident à éviter un peu les EIMI. Cependant, vous aurez peut-être encore besoin de les utiliser (par exemple, implémenter deux méthodes d'interface avec le même nom et la même signature). Voici les gros problèmes avec les EIMI:
- Il n'y a pas de documentation expliquant comment un type implémente spécifiquement une méthode EIMI et il n'y a aucune prise en charge de Microsoft Visual Studio IntelliSense .
- Les instances de type valeur sont encadrées lors de la conversion en interface.
- Un EIMI ne peut pas être appelé par un type dérivé.
Si vous utilisez une référence d'interface, TOUTE chaîne virtuelle pouvant être explicitement remplacée par EIMI sur n'importe quelle classe dérivée et lorsqu'un objet de ce type est transtypé sur l'interface, votre chaîne virtuelle est ignorée et l'implémentation explicite est appelée. C'est tout sauf du polymorphisme.
Les EIMI peuvent également être utilisés pour masquer les membres d'interface non fortement typés des implémentations de base d'Interfaces Framework telles que IEnumerable <T> afin que votre classe n'expose pas directement une méthode non fortement typée, mais qu'elle est syntaxiquement correcte.
Outre les autres raisons déjà indiquées, il s'agit de la situation dans laquelle une classe implémente deux interfaces différentes ayant une propriété/méthode portant le même nom et la même signature.
/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
string Title { get; }
string ISBN { get; }
}
/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
string Title { get; }
string Forename { get; }
string Surname { get; }
}
/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
/// <summary>
/// This method is shared by both Book and Person
/// </summary>
public string Title
{
get
{
string personTitle = "Mr";
string bookTitle = "The Hitchhikers Guide to the Galaxy";
// What do we do here?
return null;
}
}
#region IPerson Members
public string Forename
{
get { return "Lee"; }
}
public string Surname
{
get { return "Oades"; }
}
#endregion
#region IBook Members
public string ISBN
{
get { return "1-904048-46-3"; }
}
#endregion
}
Ce code est compilé et fonctionne correctement, mais la propriété Title est partagée.
Il est clair que nous voudrions que la valeur de Title restituée dépende du fait que nous traitions Class1 comme un livre ou une personne. C'est à ce moment que nous pouvons utiliser l'interface explicite.
string IBook.Title
{
get
{
return "The Hitchhikers Guide to the Galaxy";
}
}
string IPerson.Title
{
get
{
return "Mr";
}
}
public string Title
{
get { return "Still shared"; }
}
Notez que les définitions d'interface explicites sont supposées être publiques. Par conséquent, vous ne pouvez pas les déclarer publiques (ou autrement) de manière explicite.
Notez également que vous pouvez toujours avoir une version "partagée" (comme indiqué ci-dessus), mais bien que cela soit possible, l'existence d'une telle propriété est discutable. Peut-être pourrait-il être utilisé comme implémentation par défaut de Title - afin que le code existant ne doive pas être modifié pour convertir Class1 en IBook ou IPerson.
Si vous ne définissez pas le titre "partagé" (implicite), les utilisateurs de Class1 doivent convertissent explicitement les instances de Class1 en IBook ou IPerson en premier - sinon le code ne sera pas compilé.
J'utilise la plupart du temps une implémentation d'interface explicite. Voici les principales raisons.
Le refactoring est plus sûr
Lorsque vous modifiez une interface, il est préférable que le compilateur puisse la vérifier. Ceci est plus difficile avec les implémentations implicites.
Deux cas courants viennent à l’esprit:
Ajout d'une fonction à une interface, où une classe existante qui implémente cette interface a déjà une méthode avec la même signature que la nouvelle . Cela peut conduire à un comportement inattendu et m'a souvent mordu à plusieurs reprises. Il est difficile de "voir" lors du débogage car cette fonction n'est probablement pas située avec les autres méthodes d'interface du fichier (problème d'auto-documentation mentionné ci-dessous).
Suppression d'une fonction d'une interface . Les méthodes implémentées implicitement seront du code soudain mort, mais les méthodes implémentées explicitement seront interceptées par une erreur de compilation. Même si le code mort est bon à garder, je veux être obligé de le revoir et de le promouvoir.
Il est regrettable que C # ne dispose pas d'un mot clé qui nous oblige à marquer une méthode comme une implémentation implicite, afin que le compilateur puisse effectuer les vérifications supplémentaires. Les méthodes virtuelles ne rencontrent aucun des problèmes ci-dessus en raison de l'utilisation obligatoire de 'override' et de 'new'.
Remarque: pour les interfaces fixes ou changeant rarement (généralement à partir des API du fournisseur), cela ne pose pas de problème. Pour mes propres interfaces, cependant, je ne peux pas prédire quand/comment elles vont changer.
C'est auto-documenté
Si je vois 'public bool Execute ()' dans une classe, il faudra du travail supplémentaire pour comprendre que cela fait partie d'une interface. Quelqu'un devra probablement le commenter en disant cela, ou le placer dans un groupe d'implémentations d'interfaces, le tout dans une région ou un regroupement commentant "implémentation de ITask". Bien sûr, cela ne fonctionne que si l'en-tête du groupe n'est pas hors écran.
Considérant que: 'bool ITask.Execute ()' est clair et sans ambiguïté.
Séparer clairement la mise en oeuvre de l'interface
Je considère les interfaces comme étant plus "publiques" que les méthodes publiques, car elles sont conçues pour exposer un peu de la surface du type concret. Ils réduisent le type à une capacité, à un comportement, à un ensemble de traits, etc. Et lors de la mise en œuvre, je pense qu'il est utile de garder cette séparation.
En parcourant le code d'une classe, lorsque je découvre des implémentations d'interface explicites, mon cerveau passe en mode "contrat de code". Souvent, ces implémentations sont simplement transmises à d’autres méthodes, mais parfois, elles effectuent une vérification supplémentaire de l’état ou des paramètres, la conversion des paramètres entrants afin de mieux correspondre aux exigences internes, ou même la traduction à des fins de gestion des versions (plusieurs générations d’interfaces menant à des implémentations communes).
(Je réalise que les publics sont aussi des contrats de code, mais les interfaces sont beaucoup plus solides, en particulier dans une base de code pilotée par interface où l'utilisation directe de types concrets est généralement un signe de code interne uniquement.)
Connexes: Raison 2 ci-dessus par Jon .
Et ainsi de suite
Plus les avantages déjà mentionnés dans d'autres réponses ici:
Ce n'est pas que du plaisir et du bonheur. Il y a des cas où je m'en tiens à des implicites:
En outre, il peut être difficile de faire le casting lorsque vous avez le type concret et que vous souhaitez appeler une méthode d'interface explicite. Je traite cela de l'une des deux manières suivantes:
public IMyInterface I { get { return this; } }
(qui devrait être en ligne) et appelez foo.I.InterfaceMethod()
. Si plusieurs interfaces ont besoin de cette fonctionnalité, développez le nom au-delà de moi (dans mon expérience, il est rare que j'en ai besoin).Si vous implémentez explicitement, vous ne pourrez référencer les membres de l'interface que par le biais d'une référence du type de l'interface. Une référence qui est le type de la classe d'implémentation n'exposera pas ces membres d'interface.
Si votre classe d'implémentation n'est pas publique, à l'exception de la méthode utilisée pour créer la classe (qui peut être une fabrique ou un conteneur IoC ), et à l'exception des méthodes d'interface (bien sûr), je ne le fais pas. t voyez aucun avantage à implémenter explicitement des interfaces.
Sinon, une implémentation explicite des interfaces garantit que les références à votre classe d'implémentation concrète ne sont pas utilisées, ce qui vous permet de modifier cette implémentation ultérieurement. "S'assure", je suppose, est "l'avantage". Une implémentation bien factorisée peut accomplir cela sans implémentation explicite.
L'inconvénient, à mon avis, est que vous rencontrerez vous-même des types de casting vers/depuis l'interface dans le code d'implémentation qui a accès aux membres non publics.
Comme beaucoup de choses, l’avantage est l’inconvénient (et vice-versa). L'implémentation explicite d'interfaces garantira que votre code d'implémentation de classe concrète n'est pas exposé.
Une implémentation d'interface implicite consiste à utiliser une méthode avec la même signature de l'interface.
Une implémentation d'interface explicite est l'endroit où vous déclarez explicitement à quelle interface appartient la méthode.
interface I1
{
void implicitExample();
}
interface I2
{
void explicitExample();
}
class C : I1, I2
{
void implicitExample()
{
Console.WriteLine("I1.implicitExample()");
}
void I2.explicitExample()
{
Console.WriteLine("I2.explicitExample()");
}
}
Chaque membre de classe qui implémente une interface exporte une déclaration qui est sémantiquement similaire à la façon dont les déclarations d'interface VB.NET sont écrites, par exemple.
Public Overridable Function Foo() As Integer Implements IFoo.Foo
Bien que le nom du membre du groupe corresponde souvent à celui du membre de l'interface et que le membre du groupe soit souvent public, aucune de ces choses n'est requise. On peut aussi déclarer:
Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo
Dans ce cas, la classe et ses dérivés seraient autorisés à accéder à un membre de la classe en utilisant le nom IFoo_Foo
, mais le monde extérieur ne pourrait accéder à ce membre particulier qu'en effectuant une conversion en IFoo
. Une telle approche est souvent bonne dans les cas où une méthode d'interface aura un comportement spécifié sur toutes les implémentations, mais tile comportement sur seulement quelques-uns [par exemple. Le comportement spécifié pour la méthode IList<T>.Add
d'une collection en lecture seule consiste à lancer NotSupportedException
]. Malheureusement, le seul moyen d'implémenter l'interface en C # est:
int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }
Pas aussi gentil.
Une utilisation importante de la mise en œuvre d'interface explicite est le besoin d'implémenter des interfaces avec une visibilité mixte .
Le problème et la solution sont bien expliqués dans l'article interface interne C #.
Par exemple, si vous souhaitez protéger la fuite d'objets entre les couches d'application, cette technique vous permet de spécifier une visibilité différente des membres pouvant provoquer la fuite.