684 Stimmen

C#-Schnittstellen. Implizite Implementierung versus explizite Implementierung

Was sind die Unterschiede bei der Implementierung von Schnittstellen? implizit y ausdrücklich in C#?

Wann sollten Sie implizit und wann explizit verwenden?

Gibt es Vor- und/oder Nachteile für das eine oder das andere?


Die offiziellen Richtlinien von Microsoft (aus der ersten Ausgabe Rahmengestaltungsrichtlinien ) besagt, dass die Verwendung expliziter Implementierungen wird nicht empfohlen , da der Code dadurch ein unerwartetes Verhalten zeigt.

Ich denke, diese Leitlinie ist sehr gültig in einer Vor-IoC-Zeit wenn Sie die Dinge nicht als Schnittstellen weitergeben.

Könnte jemand auch auf diesen Aspekt eingehen?

0 Stimmen

Vollständigen Artikel über C#-Schnittstellen lesen : planetofcoders.com/c-schnittstellen

1 Stimmen

Ja, explizite Schnittstellen sollten vermieden werden, und ein professionellerer Ansatz wäre die Implementierung von ISP (Schnittstellentrennungsprinzip) - hier ein ausführlicher Artikel dazu codeproject.com/Artikel/1000374/

0 Stimmen

In den meisten Fällen werden Schnittstellen nicht benötigt, denken Sie darüber nach, wer Ihren Code verwendet, niemand, also verschwenden Sie keine Zeit mit dem Versuch, die besten Schnittstellen zu implementieren, Schnittstellen erhöhen nur die Komplexität des Projekts und führen zu mehr Funktionsfehlern, als es sein sollte

18voto

Lee Oades Punkte 1509

Zusätzlich zu den 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 lässt sich kompilieren und läuft gut, aber die Eigenschaft Titel wird gemeinsam genutzt.

Wir möchten natürlich, dass der zurückgegebene Wert von Title davon abhängt, ob wir Class1 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 aus den expliziten Schnittstellendefinitionen abgeleitet wird, dass sie öffentlich sind - und Sie sie daher nicht explizit als öffentlich (oder anders) deklarieren können.

Beachten Sie auch, dass Sie immer noch eine "gemeinsame" Version haben können (wie oben gezeigt), aber während dies möglich ist, ist die Existenz einer solchen Eigenschaft fraglich. Vielleicht könnte sie als Standardimplementierung von Title verwendet werden, so dass vorhandener Code nicht geändert werden müsste, um Class1 in IBook oder IPerson zu verwandeln.

Wenn Sie den "gemeinsamen" (impliziten) Titel nicht definieren, werden die Verbraucher von Klasse1 debe Instanzen von Class1 zuerst explizit in IBook oder IPerson umwandeln - andernfalls wird der Code nicht kompiliert.

18voto

scobi Punkte 13904

Meistens verwende ich eine 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 mir in den Sinn:

  • Hinzufügen einer Funktion zu einer Schnittstelle, wenn eine bestehende Klasse, die diese Schnittstelle implementiert, bereits eine Methode mit der gleichen Signatur wie die neue hat . Dies kann zu unerwartetem Verhalten führen und hat mich schon einige Male hart getroffen. Bei der Fehlersuche ist es schwer zu "sehen", weil diese Funktion wahrscheinlich nicht zusammen mit den anderen Schnittstellenmethoden in der Datei zu finden ist (das unten erwähnte Problem der Selbstdokumentation).

  • Entfernen einer Funktion aus einer Schnittstelle . Implizit implementierte Methoden werden plötzlich zu totem Code, aber explizit implementierte Methoden werden durch einen Kompilierfehler abgefangen. Selbst wenn der tote Code gut ist, möchte ich gezwungen sein, ihn zu überprüfen und zu fördern.

Leider gibt es in C# kein Schlüsselwort, das uns zwingt, eine Methode als implizite Implementierung zu kennzeichnen, so dass der Compiler die zusätzlichen Prüfungen durchführen könnte. Virtuelle Methoden haben keines der oben genannten Probleme aufgrund der erforderlichen Verwendung von 'override' und 'new'.

Hinweis: Für feste oder sich selten ändernde Schnittstellen (typischerweise von Anbieter-APIs) ist dies kein Problem. Bei meinen eigenen Schnittstellen kann ich jedoch nicht vorhersagen, wann/wie sie sich ändern werden.

Es ist selbstdokumentierend

Wenn ich in einer Klasse 'public bool Execute()' sehe, wird es zusätzliche Arbeit kosten, herauszufinden, dass es Teil einer Schnittstelle ist. Jemand wird sie wahrscheinlich mit einem entsprechenden Kommentar versehen oder sie in eine Gruppe anderer Schnittstellenimplementierungen einordnen müssen, die alle unter einer Region oder einem Gruppenkommentar stehen, der besagt "implementation of ITask". Das funktioniert natürlich nur, wenn der Gruppenkopf nicht außerhalb des Bildschirms liegt

in Erwägung nachstehender Gründe 'bool ITask.Execute()' ist klar und unmissverständlich.

Klare Trennung der Schnittstellenimplementierung

Ich betrachte Schnittstellen als "öffentlicher" als öffentliche Methoden, weil sie so gestaltet sind, dass sie nur einen kleinen Teil der Oberfläche des konkreten Typs offenlegen. Sie reduzieren den Typ auf eine Fähigkeit, ein Verhalten, eine Reihe von Merkmalen usw. Und bei der Implementierung halte ich es für sinnvoll, diese Trennung beizubehalten.

Wenn ich mir den Code einer Klasse ansehe und auf explizite Schnittstellenimplementierungen stoße, schaltet mein Gehirn in den Modus "Codevertrag". Oft leiten diese Implementierungen einfach an andere Methoden weiter, aber manchmal führen sie zusätzliche Zustands-/Parameterprüfungen durch, konvertieren eingehende Parameter, um den internen Anforderungen besser zu entsprechen, oder übersetzen sogar zu Versionszwecken (d. h. mehrere Generationen von Schnittstellen, die alle auf gemeinsame Implementierungen zurückgehen).

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

Verwandt: Grund 2 oben von Jon .

Und so weiter

Hinzu kommen die Vorteile, die bereits in anderen Antworten hier genannt wurden:

Probleme

Es geht nicht nur um Spaß und Glück. Es gibt einige Fälle, in denen ich mich an Andeutungen halte:

  • Werttypen, denn das erfordert Boxing und geringere Leistung. Dies ist keine strikte Regel und hängt von der Schnittstelle ab und davon, wie sie verwendet werden soll. IComparable? Implizit. IFormattable? Wahrscheinlich explizit.
  • Triviale Systemschnittstellen, die Methoden haben, die häufig direkt aufgerufen werden (wie IDisposable.Dispose).

Außerdem kann es mühsam sein, das Casting durchzuführen, wenn man tatsächlich den konkreten Typ hat und eine explizite Schnittstellenmethode aufrufen möchte. Ich gehe damit auf zwei Arten um:

  1. Fügen Sie Publics hinzu und lassen Sie die Methoden der Schnittstelle für die Implementierung an diese weiterleiten. Dies geschieht in der Regel bei einfacheren Schnittstellen, wenn man intern arbeitet.
  2. (Meine bevorzugte Methode) Fügen Sie eine public IMyInterface I { get { return this; } } (das inlined werden sollte) und rufen foo.I.InterfaceMethod() . Bei mehreren Schnittstellen, die diese Fähigkeit benötigen, erweitern Sie den Namen über I hinaus (meiner Erfahrung nach ist es selten, dass ich diese Notwendigkeit habe).

8voto

Bill Punkte 2291

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

Wenn Ihre implementierende Klasse nicht öffentlich ist, mit Ausnahme der Methode, mit der die Klasse erstellt wird (dies könnte eine Fabrik oder IoC Container), und abgesehen von den Schnittstellenmethoden (natürlich), dann sehe ich keinen Vorteil darin, Schnittstellen explizit zu implementieren.

Andernfalls stellt die explizite Implementierung von Schnittstellen sicher, dass Verweise auf Ihre konkrete implementierende Klasse nicht verwendet werden, so dass Sie diese Implementierung zu einem späteren Zeitpunkt ändern können. "Sicherstellen" ist wohl der "Vorteil". Eine gut durchdachte Implementierung kann dies auch ohne explizite Implementierung erreichen.

Der Nachteil besteht meiner Meinung nach darin, dass Sie im Implementierungscode, der Zugriff auf nicht-öffentliche Mitglieder hat, Typen in/aus der Schnittstelle casten müssen.

Wie bei vielen Dingen ist der Vorteil auch hier der Nachteil (und umgekehrt). Durch die explizite Implementierung von Schnittstellen wird sichergestellt, dass der Implementierungscode Ihrer konkreten Klasse nicht offengelegt wird.

6voto

supercat Punkte 72939

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

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

Obwohl der Name des Klassenmitglieds oft mit dem des Schnittstellenmitglieds übereinstimmt und das Klassenmitglied oft öffentlich ist, ist beides nicht erforderlich. Man kann auch deklarieren:

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

In diesem Fall wäre es der Klasse und ihren Ableitungen erlaubt, auf ein Klassenmitglied mit dem Namen IFoo_Foo aber die Außenwelt kann nur auf dieses bestimmte Mitglied zugreifen, indem sie auf IFoo . Ein solcher Ansatz ist oft dann sinnvoll, wenn eine Schnittstellenmethode angegeben Verhalten bei allen Implementierungen, aber nützlich Verhalten auf nur einige [z. B. das angegebene Verhalten für eine schreibgeschützte Sammlung der IList<T>.Add Methode ist es, die NotSupportedException ]. Leider ist der einzige richtige Weg, 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.

5voto

Yochai Timmer Punkte 46099

Bei einer impliziten Schnittstellenimplementierung haben Sie 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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X