Question Interfaces C # Implémentation implicite versus implémentation explicite


Quelles sont les différences dans la mise en œuvre des 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 à l'un ou à l'autre?


Les directives officielles de Microsoft (à partir de la première édition Directives de conception du cadre) stipule que l'utilisation d'implémentations explicites n'est pas recommandée, car il donne au code un comportement inattendu.

Je pense que cette ligne directrice est très valide dans un temps pré-IoC, quand vous ne faites pas passer les choses comme des interfaces.

Quelqu'un pourrait-il toucher à cet aspect aussi?


558
2017-09-27 10:56


origine


Réponses:


Implicite est lorsque vous définissez votre interface via un membre de votre classe. Explicite est lorsque vous définissez des méthodes dans votre classe sur l'interface. Je sais que cela semble confus 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 en tant que:

void ICollection.CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

La différence étant implicitement est accessible à travers votre classe que vous avez créée quand elle est castée en tant que classe ainsi que quand elle est castée en tant qu'interface. L'implémentation explicite permet uniquement d'être accessible lors de la conversion en tant qu'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 / ne pas l'utiliser que d'autres posteront.

Voir le prochain message dans ce fil pour un excellent raisonnement derrière chacun.


441
2017-09-27 11:07



La définition implicite consiste simplement à ajouter les méthodes / propriétés, etc. demandées 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 travaillez directement avec l'interface, et non l'implémentation sous-jacente. Ceci est préféré dans la plupart des cas.

  1. En travaillant directement avec l'interface, vous ne reconnaissez pas, et coupler votre code à l'implémentation sous-jacente.
  2. Dans le cas où vous avez déjà, disons, un nom de propriété publique dans votre code et vous voulez mettre en œuvre une interface qui a également un La propriété Name, en le faisant explicitement, gardera les deux séparés. Même s'ils faisaient la même chose, je déléguerais toujours le explicit appel à la propriété Name. Vous ne savez jamais, vous pouvez vouloir changer comment le nom fonctionne pour la classe normale et comment Name, l'interface la propriété fonctionne plus tard.
  3. Si vous implémentez une interface implicitement, votre classe expose maintenant nouveaux comportements qui pourraient ne concerner qu'un client du interface et cela signifie que vous ne gardez pas vos classes succinctes assez (mon opinion).

179
2017-09-27 11:09



En plus des excellentes réponses déjà fournies, il y a des cas où une implémentation explicite est OBLIGATOIRE pour que le compilateur puisse comprendre ce qui est requis. Jeter un coup d'œil à IEnumerable<T> en tant qu'exemple primordial qui viendra probablement 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> met en oeuvre IEnumerable, donc nous devons aussi. Mais attendez, à la fois le générique et la version 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 bien. Comment le compilateur décide-t-il de l'utiliser? Il vous oblige à avoir, au plus, une définition implicite, alors il peut résoudre tout ce dont il a besoin.

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 pour IEnumerable fonctionne parce que dans la fonction le compilateur sait que le type réel de la variable est un StringList, et c'est ainsi qu'il résout l'appel de fonction. Nifty petit fait pour mettre en œuvre certaines des couches d'abstraction certaines des interfaces de base. NET semblent avoir accumulé.


63
2017-09-27 11:59



Raison # 1

J'ai tendance à utiliser une implémentation d'interface explicite quand je veux décourager la "programmation d'une implémentation" (Principes de conception des modèles de conception).

Par exemple, dans un MVPapplication web basée sur:

public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

Maintenant, une autre classe (comme un présentateur) est moins susceptible de dépendre de l'implémentation de StandardNavigator et plus susceptible de dépendre de l'interface INavigator (puisque l'implémentation devrait être castée en une interface pour utiliser la méthode Redirect).

Raison # 2

Une autre raison pour laquelle je pourrais aller avec une implémentation d'interface explicite serait de garder l'interface "par défaut" d'une classe plus propre. Par exemple, si je développais un ASP.NET contrôle du serveur, je pourrais vouloir deux interfaces:

  1. L'interface principale de la classe, utilisée par les développeurs de pages Web; et
  2. Une interface "cachée" utilisée par le présentateur que je développe pour gérer la logique du contrôle

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 page Web ne souhaite pas remplir la liste; à la place, ils veulent simplement pouvoir sélectionner un client par GUID ou obtenir le GUID du client sélectionné. Un présentateur remplit la zone sur le chargement de la première page et ce présentateur est 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 la page Web n'a jamais besoin de connaître son existence.

Mais ce n'est pas un boulet de canon argenté

Je ne recommanderais pas toujours d'utiliser des implémentations d'interface explicites. Ce ne sont que deux exemples où ils pourraient être utiles.


28
2018-06-14 01:31



Pour citer Jeffrey Richter de CLR via C #
(EIMI veux dire Explicit jenterface Method jemise en œuvre)

Il est extrêmement important pour vous de   comprendre certaines ramifications   existe lorsque vous utilisez EIMIs. Et à cause de   ces ramifications, vous devriez essayer de   éviter les EIMI autant que possible.   Heureusement, les interfaces génériques aident   vous évitez un peu les EIMI. Mais là   peut encore être des moments où vous aurez besoin   les utiliser (comme la mise en œuvre de deux   méthodes d'interface avec le même nom   et signature). Voici les grands   problèmes avec EIMI:

  • Il n'y a pas de documentation expliquant comment un type spécifiquement   implémente une méthode EIMI, et là   n'est pas Microsoft Visual Studio    IntelliSense soutien.
  • Les instances de type de valeur sont encadrées lorsqu'elles sont converties 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 peut être explicitement remplacée par EIMI sur une classe dérivée et lorsqu'un objet de ce type est casté vers l'interface, votre chaîne virtuelle est ignorée et l'implémentation explicite est appelée. C'est tout sauf le polymorphisme.

Les EIMI peuvent également être utilisés pour masquer des membres d'interface non fortement typés à partir d'implémentations de base d'Interfaces de Framework telles que IEnumerable <T> afin que votre classe n'expose pas directement une méthode non fortement typée, mais qu'elle soit syntaxiquement correcte.


28
2018-02-09 18:40



En plus des autres raisons déjà mentionnées, c'est la situation dans laquelle une classe implémente deux interfaces différentes qui ont une propriété / méthode avec 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 compile et s'exécute OK, mais la propriété Title est partagée.

Clairement, nous voudrions que la valeur de Title retourné dépende de si nous traitons Class1 comme un livre ou une personne. C'est quand 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 - et vous ne pouvez donc pas les déclarer publiques (ou non) explicitement.

Notez également que vous pouvez toujours avoir une version "partagée" (comme indiqué ci-dessus), mais si cela est possible, l'existence d'une telle propriété est discutable. Peut-être pourrait-il être utilisé comme implémentation par défaut de Title - de sorte que le code existant ne devrait pas être modifié pour convertir Class1 en IBook ou IPerson.

Si vous ne définissez pas le titre "partagé" (implicite), les consommateurs de Class1 doit transtyper les instances de Class1 en IBook ou IPerson en premier, sinon le code ne compilera pas.


17
2017-10-01 13:08



J'utilise une implémentation d'interface explicite la plupart du temps. Voici les principales raisons.

Le refactoring est plus sûr

Lorsque vous changez une interface, il est préférable que le compilateur puisse la vérifier. C'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 possède déjà une méthode avec la même signature que la nouvelle. Cela peut conduire à un comportement inattendu, et m'a mordu plusieurs fois. Il est difficile de "voir" lors du débogage car cette fonction n'est probablement pas localisée avec les autres méthodes d'interface dans le fichier (le problème d'auto-documentation mentionné ci-dessous).

  • Supprimer une fonction d'une interface. Les méthodes implémentées implicitement seront du code soudainement 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 # n'ait pas de mot-clé qui nous oblige à marquer une méthode comme une implémentation implicite, donc le compilateur pourrait faire les vérifications supplémentaires. Les méthodes virtuelles n'ont aucun des problèmes ci-dessus en raison de l'utilisation obligatoire de 'override' et 'new'.

Remarque: pour les interfaces fixes ou rarement modifiées (généralement issues des API du fournisseur), ce n'est pas un problème. Pour mes propres interfaces, cependant, je ne peux pas prédire quand et comment ils vont changer.

C'est auto-documenté

Si je vois 'public bool Execute ()' dans une classe, il faudra du travail supplémentaire pour comprendre qu'il fait partie d'une interface. Quelqu'un devra probablement le commenter en le disant, ou le mettre dans un groupe d'implémentations d'interface, le tout sous une région ou un commentaire de groupe disant "implémentation d'ITask". Bien sûr, cela ne fonctionne que si l'en-tête du groupe n'est pas en dehors de l'écran.

Attendu que: 'bool ITask.Execute ()' est clair et non ambigu.

Séparation claire de l'implémentation de l'interface

Je pense que les interfaces sont plus «publiques» que les méthodes publiques parce qu'elles sont conçues pour exposer juste un peu de la surface du type concret. Ils réduisent le type à une capacité, un comportement, un ensemble de traits, etc. Et dans la mise en œuvre, je pense qu'il est utile de garder cette séparation.

Lorsque je regarde le code d'une classe, lorsque je rencontre des implémentations d'interface explicites, mon cerveau passe en mode «contrat de code». Souvent, ces implémentations transmettent simplement à d'autres méthodes, mais effectuent parfois une vérification supplémentaire des états / paramètres, une conversion des paramètres entrants pour mieux correspondre aux exigences internes ou même une traduction à des fins de version (par exemple, plusieurs générations d'interfaces).

(Je réalise que les publics sont aussi des contrats de code, mais les interfaces sont beaucoup plus fortes, surtout dans une base de code axée sur l'interface où l'utilisation directe de types concrets est généralement un signe de code interne seulement.)

En relation: Raison 2 ci-dessus par Jon.

Etc

Plus les avantages déjà mentionnés dans d'autres réponses ici:

Problèmes

Ce n'est pas tout le plaisir et le bonheur. Il y a des cas où je m'en tiens à implicits:

  • Les types de valeur, parce que cela exigera la boxe et la perf inférieure. Ce n'est pas une règle stricte et dépend de l'interface et de la façon dont elle est destinée à être utilisée. IComparable? Implicite. IFormattable? Probablement explicite.
  • Les interfaces système triviales qui ont des méthodes fréquemment appelées directement (comme IDisposable.Dispose).

En outre, il peut être difficile de faire le casting lorsque vous avez en fait le type concret et que vous voulez appeler une méthode d'interface explicite. Je fais face à cela de deux façons:

  1. Ajoutez des publics et ayez les méthodes d'interface à leur disposition pour la mise en œuvre. Se produit généralement avec des interfaces plus simples lorsque vous travaillez en interne.
  2. (Ma méthode préférée) Ajouter un public IMyInterface I { get { return this; } } (qui devrait être intégré) et appeler foo.I.InterfaceMethod(). Si plusieurs interfaces ont besoin de cette capacité, développez le nom au-delà de I (dans mon expérience, il est rare que j'ai ce besoin).

10
2017-08-15 08:27



Si vous implémentez explicitement, vous ne pourrez référencer les membres de l'interface qu'à travers une référence du type de l'interface. Une référence qui est le type de la classe d'implémentation n'expose 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 usine ou IoC conteneur), et à l'exception des méthodes d'interface (bien sûr), je ne vois aucun avantage à implémenter explicitement des interfaces.

Sinon, l'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 ceci sans implémentation explicite.

L'inconvénient, à mon avis, est que vous allez vous retrouver en train de lancer des types vers / à partir de l'interface dans le code d'implémentation qui a accès à des membres non publics.

Comme beaucoup de choses, l'avantage est le désavantage (et vice versa). L'implémentation explicite des interfaces garantit que votre code d'implémentation de classe concret n'est pas exposé.


8
2017-09-27 11:12



Une implémentation d'interface implicite est l'endroit où vous avez 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()");
    }
}

MSDN: implémentations d'interfaces implicites et explicites


6
2018-05-26 14:12