C # -Schnittstellen. Implizite Implementierung versus explizite Implementierung

632

Was sind die Unterschiede bei der impliziten und expliziten Implementierung von Schnittstellen in C #?

Wann sollten Sie implizit und wann explizit verwenden?

Gibt es Vor- und / oder Nachteile für den einen oder anderen?


Die offiziellen Richtlinien von Microsoft (aus den Framework Design Guidelines der ersten Ausgabe ) besagen, dass die Verwendung expliziter Implementierungen nicht empfohlen wird , da dies dem Code unerwartetes Verhalten verleiht.

Ich denke, diese Richtlinie ist in einer Zeit vor dem IoC sehr gültig , wenn Sie Dinge nicht als Schnittstellen weitergeben.

Könnte jemand auch diesen Aspekt ansprechen?

Seb Nilsson
quelle
Lesen Sie den vollständigen Artikel über C # -Schnittstellen: planetofcoders.com/c-interfaces
Gaurav Agrawal
Ja, explizite Schnittstellen vermieden werden sollte und professioneller Ansatz würde ISP (Interface Segregation Prinzip) implementieren hier ein Detail Artikel auf derselben ist codeproject.com/Articles/1000374/...
Shivprasad Koirala

Antworten:

492

Implizit ist, wenn Sie Ihre Schnittstelle über ein Mitglied in Ihrer Klasse definieren. Explizit ist, wenn Sie Methoden innerhalb Ihrer Klasse auf der Schnittstelle definieren. Ich weiß, das klingt verwirrend, aber hier ist was ich meine: IList.CopyTowürde implizit implementiert werden als:

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

und explizit als:

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

Der Unterschied besteht darin, dass Sie durch die implizite Implementierung über die von Ihnen erstellte Klasse auf die Schnittstelle zugreifen können, indem Sie die Schnittstelle als diese Klasse und als Schnittstelle selbst umwandeln. Durch die explizite Implementierung können Sie nur auf die Schnittstelle zugreifen, indem Sie sie als Schnittstelle selbst umwandeln.

MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.

Ich verwende explizit hauptsächlich, um die Implementierung sauber zu halten oder wenn ich zwei Implementierungen benötige. Trotzdem benutze ich es selten.

Ich bin mir sicher, dass es weitere Gründe gibt, explizit zu verwenden / nicht zu verwenden, die andere veröffentlichen werden.

Im nächsten Beitrag in diesem Thread finden Sie hervorragende Gründe dafür.

mattlant
quelle
8
Ich weiß, dass dieser Beitrag alt ist, aber ich fand ihn sehr nützlich - eine Sache zu beachten, wenn es nicht klar ist, weil es mir nicht so war, war, dass im Beispiel der implizite das publicSchlüsselwort hat ... sonst erhalten Sie eine Fehlermeldung
jharr100
Jeffrey Richters CLR über C # 4 ed ch 13 zeigt ein Beispiel, in dem Casting nicht benötigt wird: interne Struktur SomeValueType: IComparable {private Int32 m_x; public SomeValueType (Int32 x) {m_x = x; } public Int32 CompareTo (SomeValueType other) {...);} Int32 IComparable.CompareTo (Object other) {return CompareTo ((SomeValueType) other); }} public static void Main () {SomeValueType v = neuer SomeValueType (0); Objekt o = neues Objekt (); Int32 n = v.CompareTo (v); // Kein Boxen n = v.CompareTo (o); // Kompilierungsfehler}
Andy Dent
1
Heute bin ich auf eine seltene Situation gestoßen, die die Verwendung einer expliziten Schnittstelle erforderte: Eine Klasse mit einem Feld, das von einem Schnittstellen-Builder generiert wurde, der das Feld als privat erstellt (Xamarin für iOS mit iOS-Storyboard). Und eine Schnittstelle, an der es sinnvoll war, dieses Feld verfügbar zu machen (nur öffentlich lesbar). Ich hätte den Namen des Getters in der Schnittstelle ändern können, aber der vorhandene Name war der logischste Name für das Objekt. Also habe ich stattdessen eine explizite Implementierung durchgeführt, die sich auf das private Feld bezieht : UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }.
ToolmakerSteve
1
Die Schrecken der Programmierung. Gut entlarvt!
Liquid Core
1
@ToolmakerSteve Eine andere Situation (eher häufig), die die explizite Implementierung mindestens eines Schnittstellenmitglieds erfordert, ist die Implementierung mehrerer Schnittstellen, die Mitglieder mit derselben Signatur, aber unterschiedlichen Rückgabetypen haben. Dies kann wegen der Schnittstelle Vererbung geschehen, wie sie mit IEnumerator<T>.Current, IEnumerable<T>.GetEnumerator()und ISet<T>.Add(T). Dies wird in einer anderen Antwort erwähnt .
Phoog
201

Eine implizite Definition wäre, einfach die von der Schnittstelle geforderten Methoden / Eigenschaften usw. als öffentliche Methoden direkt zur Klasse hinzuzufügen.

Durch die explizite Definition werden die Mitglieder nur dann verfügbar gemacht, wenn Sie direkt mit der Schnittstelle arbeiten, und nicht mit der zugrunde liegenden Implementierung. Dies ist in den meisten Fällen bevorzugt.

  1. Wenn Sie direkt mit der Schnittstelle arbeiten, bestätigen Sie dies nicht und koppeln Ihren Code an die zugrunde liegende Implementierung.
  2. Wenn Sie beispielsweise bereits einen öffentlichen Eigenschaftsnamen in Ihrem Code haben und eine Schnittstelle implementieren möchten, die auch eine Name-Eigenschaft enthält, werden die beiden durch eine explizite Ausführung getrennt. Selbst wenn sie dasselbe tun würden, würde ich den expliziten Aufruf dennoch an die Name-Eigenschaft delegieren. Sie wissen nie, dass Sie möglicherweise ändern möchten, wie Name für die normale Klasse funktioniert und wie Name, die Eigenschaft interface, später funktioniert.
  3. Wenn Sie eine Schnittstelle implizit implementieren, zeigt Ihre Klasse jetzt neue Verhaltensweisen an, die möglicherweise nur für einen Client der Schnittstelle relevant sind, und dies bedeutet, dass Sie Ihre Klassen nicht kurz genug halten (meiner Meinung nach).
Phil Bennett
quelle
2
Sie machen hier einige gute Punkte. besonders A. Ich gebe meine Klassen normalerweise sowieso als Schnittstelle weiter, aber ich habe nie wirklich darüber nachgedacht, aus dieser Perspektive.
Mattlant
5
Ich bin mir nicht sicher, ob ich Punkt C zustimme. Ein Cat-Objekt könnte IEatable implementieren, aber Eat () ist ein grundlegender Teil der Sache. Es würde Fälle geben, in denen Sie Eat () für eine Katze einfach aufrufen möchten, wenn Sie das 'raw'-Objekt verwenden und nicht über die IEatable-Schnittstelle, nicht wahr?
LegendLength
59
Ich kenne einige Orte, an denen Cates tatsächlich IEatablekeine Einwände gibt.
Humberto
26
Ich bin mit all dem völlig nicht einverstanden und würde sagen, dass die Verwendung expliziter Schnittstellen das Rezept einer Katastrophe ist und nicht OOP oder OOD nach ihrer Definition (siehe meine Antwort über Polymorphismus)
Valentin Kuzub
2
Siehe auch: stackoverflow.com/questions/4103300/…
Zack Jannsen
68

Zusätzlich zu den bereits bereitgestellten hervorragenden Antworten ist in einigen Fällen eine explizite Implementierung erforderlich, damit der Compiler herausfinden kann, was erforderlich ist. Schauen Sie sich IEnumerable<T>ein Paradebeispiel an, das wahrscheinlich ziemlich oft auftaucht.

Hier ist ein Beispiel:

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
}

Hier IEnumerable<string>implementiert IEnumerable, daher müssen wir auch. Aber Moment mal, sowohl die generische als auch die normale Version implementieren Funktionen mit derselben Methodensignatur (C # ignoriert den Rückgabetyp dafür). Dies ist völlig legal und in Ordnung. Wie löst der Compiler die zu verwendende Lösung auf? Es zwingt Sie, höchstens eine implizite Definition zu haben, und kann dann alles auflösen, was es braucht.

dh.

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: Die kleine Indirektion in der expliziten Definition für IEnumerable funktioniert, weil der Compiler innerhalb der Funktion weiß, dass der tatsächliche Typ der Variablen eine StringList ist, und auf diese Weise den Funktionsaufruf auflöst. Eine kleine Tatsache für die Implementierung einiger Abstraktionsebenen, die einige der .NET-Kernschnittstellen anscheinend angehäuft haben.

Matthew Scharley
quelle
5
@Tassadaque: Nach 2 Jahren kann ich nur noch "Gute Frage" sagen. Ich habe keine Ahnung, außer vielleicht, dass ich diesen Code von etwas kopiert habe, an dem ich gearbeitet habe, wo er war abstract.
Matthew Scharley
4
@Tassadaque, Sie haben Recht, aber ich denke, der Punkt von Matthew Scharleys Beitrag oben ist nicht verloren.
Funkymushroom
35

Jeffrey Richter von CLR via C # zu zitieren
( EIMI bedeutet E xplicit I nterface M ethode I UMSETZUNG)

Es ist von entscheidender Bedeutung, dass Sie einige Konsequenzen verstehen, die bei der Verwendung von EIMIs auftreten. Und aufgrund dieser Konsequenzen sollten Sie versuchen, EIMIs so weit wie möglich zu vermeiden. Glücklicherweise helfen Ihnen generische Schnittstellen dabei, EIMIs ein wenig zu vermeiden. Es kann jedoch vorkommen, dass Sie sie verwenden müssen (z. B. die Implementierung von zwei Schnittstellenmethoden mit demselben Namen und derselben Signatur). Hier sind die großen Probleme mit EIMIs:

  • Es gibt keine Dokumentation, in der erläutert wird, wie ein Typ eine EIMI-Methode speziell implementiert, und es gibt keine Unterstützung für Microsoft Visual Studio IntelliSense .
  • Werttypinstanzen werden beim Umwandeln in eine Schnittstelle eingerahmt.
  • Ein EIMI kann nicht von einem abgeleiteten Typ aufgerufen werden.

Wenn Sie eine Schnittstellenreferenz verwenden, kann JEDE virtuelle Kette für jede abgeleitete Klasse explizit durch EIMI ersetzt werden. Wenn ein Objekt dieses Typs in die Schnittstelle umgewandelt wird, wird Ihre virtuelle Kette ignoriert und die explizite Implementierung aufgerufen. Das ist alles andere als Polymorphismus.

EIMIs können auch verwendet werden, um nicht stark typisierte Schnittstellenelemente vor grundlegenden Implementierungen von Framework Interfaces wie IEnumerable <T> auszublenden, sodass Ihre Klasse eine nicht stark typisierte Methode nicht direkt verfügbar macht, sondern syntaktisch korrekt ist.

Valentin Kuzub
quelle
2
Die Neuimplementierung von Schnittstellen ist zwar legal, aber im Allgemeinen bestenfalls zweifelhaft. Explizite Implementierungen sollten im Allgemeinen entweder direkt oder über eine Wrapping-Logik, die für abgeleitete Klassen bindend sein sollte, an eine virtuelle Methode gekettet werden. Zwar ist es durchaus möglich, Schnittstellen auf eine Weise zu verwenden, die den richtigen OOP-Konventionen widerspricht, dies bedeutet jedoch nicht, dass sie nicht besser verwendet werden können.
Superkatze
4
@ Valentin Wofür stehen EIMI und IEMI?
Dzienny
Implementierung der expliziten Schnittstellenmethode
scobi
5
-1 für "Im Allgemeinen sehe ich Schnittstellen als (bestenfalls) Semi-OOP-Funktion, sie bietet Vererbung, aber keinen echten Polymorphismus." Ich bin absolut anderer Meinung. Im Gegenteil, bei Schnittstellen dreht sich alles um Polymorphismus und nicht primär um Vererbung. Sie weisen einem Typ mehrere Klassifikationen zu. Vermeiden Sie IEMI, wenn Sie können, und delegieren Sie, wie @supercat vorgeschlagen hat, wenn Sie nicht können. Vermeiden Sie keine Schnittstellen.
Aluan Haddad
1
"Ein EIMI kann nicht von einem abgeleiteten Typ aufgerufen werden." << WAS? Das ist nicht wahr. Wenn ich eine Schnittstelle explizit für einen Typ implementiere und dann von diesem Typ ableite, kann ich sie trotzdem in die Schnittstelle umwandeln, um die Methode aufzurufen, genau wie ich es für den Typ tun müsste, auf dem sie implementiert ist. Also nicht sicher, wovon du sprichst. Selbst innerhalb des abgeleiteten Typs kann ich einfach "dies" in die betreffende Schnittstelle umwandeln, um die explizit implementierte Methode zu erreichen.
Triynko
34

Grund Nr. 1

Ich neige dazu, eine explizite Schnittstellenimplementierung zu verwenden, wenn ich von "Programmierung auf eine Implementierung" ( Design Principles from Design Patterns ) abraten möchte .

Zum Beispiel in einer MVP- basierten Webanwendung:

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

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

Jetzt ist es weniger wahrscheinlich, dass eine andere Klasse (z. B. ein Präsentator ) von der StandardNavigator-Implementierung abhängt, als vielmehr von der INavigator-Schnittstelle (da die Implementierung in eine Schnittstelle umgewandelt werden müsste, um die Redirect-Methode verwenden zu können).

Grund # 2

Ein weiterer Grund, warum ich mich für eine explizite Schnittstellenimplementierung entscheiden könnte, wäre, die "Standard" -Schnittstellenschnittstelle einer Klasse sauberer zu halten. Wenn ich beispielsweise ein ASP.NET- Serversteuerelement entwickeln würde, möchte ich möglicherweise zwei Schnittstellen:

  1. Die primäre Schnittstelle der Klasse, die von Webseitenentwicklern verwendet wird. und
  2. Eine "versteckte" Schnittstelle, die vom Präsentator verwendet wird, um die Logik des Steuerelements zu handhaben

Es folgt ein einfaches Beispiel. Es ist ein Kombinationsfeld-Steuerelement, das Kunden auflistet. In diesem Beispiel ist der Webseitenentwickler nicht daran interessiert, die Liste zu füllen. Stattdessen möchten sie nur einen Kunden anhand der GUID auswählen oder die GUID des ausgewählten Kunden abrufen können. Ein Präsentator würde das Feld beim Laden der ersten Seite füllen, und dieser Präsentator wird vom Steuerelement gekapselt.

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; }
}

Der Präsentator füllt die Datenquelle und der Webseitenentwickler muss sich ihrer Existenz niemals bewusst sein.

Aber es ist keine silberne Kanonenkugel

Ich würde nicht empfehlen, immer explizite Schnittstellenimplementierungen zu verwenden. Dies sind nur zwei Beispiele, bei denen sie hilfreich sein könnten.

Jon Nadal
quelle
19

Zusätzlich zu den anderen bereits genannten Gründen ist dies die Situation, in der eine Klasse zwei verschiedene Schnittstellen implementiert, die eine Eigenschaft / Methode mit demselben Namen und derselben Signatur haben.

/// <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
}

Dieser Code wird kompiliert und in Ordnung ausgeführt, aber die Title-Eigenschaft wird gemeinsam genutzt.

Wir möchten natürlich, dass der Wert des zurückgegebenen Titels davon abhängt, ob wir Klasse 1 als Buch oder als Person behandeln. In diesem Fall können wir die explizite Schnittstelle verwenden.

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

Beachten Sie, dass die expliziten Schnittstellendefinitionen als öffentlich abgeleitet werden. Daher können Sie sie nicht explizit als öffentlich (oder anderweitig) deklarieren.

Beachten Sie auch, dass Sie immer noch eine "freigegebene" Version haben können (wie oben gezeigt), obwohl dies möglich ist, ist das Vorhandensein einer solchen Eigenschaft fraglich. Möglicherweise könnte es als Standardimplementierung von Title verwendet werden, sodass der vorhandene Code nicht geändert werden muss, um Class1 in IBook oder IPerson umzuwandeln.

Wenn Sie den „shared“ (impliziten) Titel nicht definieren, die Verbraucher von Class1 müssen Instanzen von Class1 zu IBook oder IPerson zuerst explizit werfen - sonst wird der Code nicht kompilieren.

Lee Oades
quelle
16

Ich benutze die meiste Zeit die explizite Schnittstellenimplementierung. Hier sind die Hauptgründe.

Refactoring ist sicherer

Wenn Sie eine Schnittstelle ändern, ist es besser, wenn der Compiler dies überprüfen kann. Dies ist bei impliziten Implementierungen schwieriger.

Zwei häufige Fälle kommen in den Sinn:

  • Hinzufügen einer Funktion zu einer Schnittstelle, bei der eine vorhandene Klasse, die diese Schnittstelle implementiert, bereits eine Methode mit derselben Signatur wie die neue hat . Dies kann zu unerwartetem Verhalten führen und hat mich mehrmals hart gebissen. Es ist schwierig, beim Debuggen "zu sehen", da diese Funktion wahrscheinlich nicht mit den anderen Schnittstellenmethoden in der Datei gefunden wird (das unten erwähnte selbstdokumentierende Problem).

  • Entfernen einer Funktion von einer Schnittstelle . Implizit implementierte Methoden sind plötzlich toter Code, aber explizit implementierte Methoden werden von Kompilierungsfehlern erfasst. Selbst wenn der tote Code gut zu behalten ist, möchte ich gezwungen sein, ihn zu überprüfen und zu bewerben.

Es ist bedauerlich, dass C # kein Schlüsselwort hat, das uns zwingt, eine Methode als implizite Implementierung zu markieren, sodass der Compiler die zusätzlichen Überprüfungen durchführen kann. Virtuelle Methoden haben aufgrund der erforderlichen Verwendung von "Überschreiben" und "Neu" keines der oben genannten Probleme.

Hinweis: Bei festen oder sich selten ändernden Schnittstellen (normalerweise von Hersteller-APIs) ist dies kein Problem. Für meine eigenen Schnittstellen kann ich jedoch nicht vorhersagen, wann / wie sie sich ändern werden.

Es ist selbstdokumentierend

Wenn ich 'public bool Execute ()' in einer Klasse sehe, ist zusätzliche Arbeit erforderlich, um herauszufinden, dass es Teil einer Schnittstelle ist. Jemand muss es wahrscheinlich kommentieren oder in eine Gruppe anderer Schnittstellenimplementierungen einfügen, alle unter einer Region oder einem Gruppenkommentar mit der Aufschrift "Implementierung von ITask". Das funktioniert natürlich nur, wenn der Gruppenkopf nicht außerhalb des Bildschirms liegt.

Während: 'bool ITask.Execute ()' klar und eindeutig ist.

Klare Trennung der Schnittstellenimplementierung

Ich halte Schnittstellen für „öffentlicher“ als öffentliche Methoden, da sie so gestaltet sind, dass nur ein kleiner Teil der Oberfläche des Betontyps freigelegt wird. Sie reduzieren den Typ auf eine Fähigkeit, ein Verhalten, eine Reihe von Merkmalen usw. Und bei der Implementierung halte ich es für nützlich, diese Trennung beizubehalten.

Wenn ich den Code einer Klasse durchschaue und auf explizite Schnittstellenimplementierungen stoße, wechselt mein Gehirn in den Modus "Codevertrag". Oft werden diese Implementierungen einfach an andere Methoden weitergeleitet, aber manchmal führen sie zusätzliche Status- / Parameterprüfungen, Konvertierungen eingehender Parameter, um den internen Anforderungen besser zu entsprechen, oder sogar Übersetzungen für Versionszwecke durch (dh mehrere Generationen von Schnittstellen, die alle auf gemeinsame Implementierungen zurückgreifen).

(Mir ist klar, dass die Öffentlichkeit auch Codeverträge sind, aber die Schnittstellen sind viel stärker, insbesondere in einer schnittstellengesteuerten Codebasis, in der die direkte Verwendung konkreter Typen normalerweise ein Zeichen für internen Code ist.)

Siehe auch: Grund 2 oben von Jon .

Und so weiter

Plus die Vorteile, die bereits in anderen Antworten hier erwähnt wurden:

Probleme

Es ist nicht alles Spaß und Glück. Es gibt einige Fälle, in denen ich mich an Implizite halte:

  • Werttypen, da dies Boxen und geringere Leistung erfordert. Dies ist keine strenge Regel und hängt von der Benutzeroberfläche und deren Verwendung ab. IComparable? Implizit. IFormattable? Wahrscheinlich explizit.
  • Triviale Systemschnittstellen mit Methoden, die häufig direkt aufgerufen werden (z. B. IDisposable.Dispose).

Es kann auch schwierig sein, das Casting durchzuführen, wenn Sie tatsächlich den konkreten Typ haben und eine explizite Schnittstellenmethode aufrufen möchten. Ich gehe auf zwei Arten damit um:

  1. Fügen Sie Öffentlichkeiten hinzu und lassen Sie die Schnittstellenmethoden für die Implementierung an diese weiterleiten. In der Regel mit einfacheren Schnittstellen, wenn intern gearbeitet wird.
  2. (Meine bevorzugte Methode) Fügen Sie ein public IMyInterface I { get { return this; } }(das inline werden sollte) hinzu und rufen Sie auf foo.I.InterfaceMethod(). Wenn mehrere Schnittstellen diese Fähigkeit benötigen, erweitern Sie den Namen über mich hinaus (meiner Erfahrung nach ist es selten, dass ich diese Notwendigkeit habe).
scobi
quelle
8

Wenn Sie explizit implementieren, können Sie die Schnittstellenelemente nur über eine Referenz referenzieren, die vom Typ der Schnittstelle ist. Eine Referenz, die der Typ der implementierenden Klasse ist, macht diese Schnittstellenmitglieder nicht verfügbar.

Wenn Ihre implementierende Klasse nicht öffentlich ist, mit Ausnahme der Methode, mit der die Klasse erstellt wurde (die eine Factory oder ein IoC- Container sein kann), und mit Ausnahme der Schnittstellenmethoden (natürlich), sehe ich keinen Vorteil bei der expliziten Implementierung Schnittstellen.

Andernfalls wird durch die explizite Implementierung von Schnittstellen sichergestellt, dass keine Verweise auf Ihre konkrete Implementierungsklasse verwendet werden, sodass Sie diese Implementierung zu einem späteren Zeitpunkt ändern können. "Sicher" ist wohl der "Vorteil". Eine gut faktorisierte Implementierung kann dies ohne explizite Implementierung erreichen.

Meiner Meinung nach besteht der Nachteil darin, dass Sie im Implementierungscode, der Zugriff auf nicht öffentliche Mitglieder hat, Typen zur / von der Schnittstelle übertragen.

Wie bei vielen Dingen ist der Vorteil der Nachteil (und umgekehrt). Durch die explizite Implementierung von Schnittstellen wird sichergestellt, dass Ihr konkreter Klassenimplementierungscode nicht verfügbar gemacht wird.

Rechnung
quelle
1
Schöne Antwort, Bill. Die anderen Antworten waren großartig, aber Sie haben einige zusätzliche objektive Gesichtspunkte (zusätzlich zu Ihren Meinungen) angegeben, die es mir leichter gemacht haben, sie zu verstehen. Wie bei den meisten Dingen gibt es Vor- und Nachteile bei impliziten oder expliziten Implementierungen, sodass Sie nur die beste für Ihr bestimmtes Szenario oder Ihren Anwendungsfall verwenden müssen. Ich würde sagen, diejenigen, die versuchen, das besser herauszufinden, werden davon profitieren, Ihre Antwort zu lesen.
Daniel Eagle
6

Bei einer impliziten Schnittstellenimplementierung verfügen Sie über eine Methode mit derselben Signatur der Schnittstelle.

Bei einer expliziten Schnittstellenimplementierung deklarieren Sie explizit, zu welcher Schnittstelle die Methode gehört.

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: implizite und explizite Schnittstellenimplementierungen

Yochai Timmer
quelle
6

Jedes Klassenmitglied, das eine Schnittstelle implementiert, exportiert eine Deklaration, die der Art und Weise, wie VB.NET-Schnittstellendeklarationen geschrieben werden, semantisch ähnlich ist, z

Public Overridable Function Foo() As Integer Implements IFoo.Foo

Obwohl der Name des Klassenmitglieds häufig mit dem des Schnittstellenmitglieds übereinstimmt und das Klassenmitglied häufig öffentlich ist, ist keines dieser Dinge erforderlich. Man kann auch erklären:

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo

In diesem Fall IFoo_Fookönnten die Klasse und ihre Derivate unter Verwendung des Namens auf ein Klassenmitglied zugreifen , aber die Außenwelt könnte nur durch Casting auf dieses bestimmte Mitglied zugreifen IFoo. Ein solcher Ansatz ist häufig in Fällen gut, in denen eine Schnittstellenmethode bei allen Implementierungen ein bestimmtes Verhalten aufweist, bei nur einigen jedoch ein nützliches Verhalten [z. B. ist das angegebene Verhalten für die IList<T>.AddMethode einer schreibgeschützten Sammlung das Auslösen NotSupportedException]. Leider ist der einzig richtige Weg, um die Schnittstelle in C # zu implementieren ,:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }

Nicht so schön.

Superkatze
quelle
2

Eine wichtige Verwendung der expliziten Schnittstellenimplementierung ist die Implementierung von Schnittstellen mit gemischter Sichtbarkeit .

Das Problem und die Lösung werden im Artikel C # Interne Schnittstelle ausführlich erläutert .

Wenn Sie beispielsweise das Auslaufen von Objekten zwischen Anwendungsebenen schützen möchten, können Sie mit dieser Technik unterschiedliche Sichtbarkeit von Elementen festlegen, die das Auslaufen verursachen können.

nrodic
quelle
2

Die vorherigen Antworten erklären, warum die explizite Implementierung einer Schnittstelle in C # (aus meist formalen Gründen) vorzuziehen ist . Es gibt jedoch eine Situation, in der eine explizite Implementierung obligatorisch ist : Um zu vermeiden, dass die Kapselung verloren geht, wenn die Schnittstelle nicht vorhanden publicist, die implementierende Klasse jedoch public.

// Given:
internal interface I { void M(); }

// Then explicit implementation correctly observes encapsulation of I:
// Both ((I)CExplicit).M and CExplicit.M are accessible only internally.
public class CExplicit: I { void I.M() { } }

// However, implicit implementation breaks encapsulation of I, because
// ((I)CImplicit).M is only accessible internally, while CImplicit.M is accessible publicly.
public class CImplicit: I { public void M() { } }

Die obige Leckage ist unvermeidbar, da gemäß der C # -Spezifikation "alle Schnittstellenmitglieder implizit öffentlichen Zugriff haben". Infolgedessen müssen implizite Implementierungen auch publicZugriff gewähren , selbst wenn die Schnittstelle selbst z internal.

Die implizite Implementierung der Schnittstelle in C # ist sehr praktisch. In der Praxis verwenden es viele Programmierer die ganze Zeit / überall ohne weitere Überlegungen. Dies führt bestenfalls zu unordentlichen Oberflächen und im schlimmsten Fall zu einer durchgesickerten Einkapselung. Andere Sprachen wie F # erlauben es nicht einmal .

Marc Sigrist
quelle