it-swarm.com.de

C # -Schnittstellen. Implizite Implementierung versus explizite Implementierung

Was sind die Unterschiede bei der Implementierung von Schnittstellen implizit und explizit in C #?

Wann sollten Sie implizit und wann explizit verwenden?

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


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

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

Könnte jemand auch diesen Aspekt ansprechen?

610
Seb Nilsson

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

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

und ausdrücklich als:

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

Der Unterschied besteht darin, dass implizit über Ihre Klasse zugegriffen werden kann, die Sie erstellt haben, als sie als diese Klasse und als die Schnittstelle umgewandelt wurde. Die explizite Implementierung ermöglicht, dass nur auf sie zugegriffen werden kann, wenn sie als Schnittstelle selbst umgewandelt wird.

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

Ich verwende explizit in erster Linie, um die Implementierung sauber zu halten, oder wenn ich zwei Implementierungen benötige. Aber egal ich benutze es selten.

Ich bin mir sicher, dass es weitere Gründe gibt, es zu benutzen/nicht zu benutzen, die andere posten werden.

Lesen Sie den nächsten Beitrag in diesem Thread, um herauszufinden, welche Gründe dahinter stehen.

467
mattlant

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

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

  1. Wenn Sie direkt mit der Schnittstelle arbeiten, erkennen 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 Namenseigenschaft hat, werden die beiden durch explizites Ausführen getrennt. Selbst wenn sie dasselbe tun würden, würde ich den expliziten Aufruf immer noch an die Name-Eigenschaft delegieren. Sie werden nie erfahren, dass Sie möglicherweise die Funktionsweise von Name für die normale Klasse und die Funktionsweise von Name für die Interface-Eigenschaft später ändern möchten.
  3. Wenn Sie eine Schnittstelle implizit implementieren, weist Ihre Klasse jetzt neue Verhaltensweisen auf, die möglicherweise nur für einen Client der Schnittstelle relevant sind. Dies bedeutet, dass Sie Ihre Klassen nicht kurz genug halten (meiner Meinung nach).
190
Phil Bennett

In einigen Fällen ist zusätzlich zu den bereits gegebenen hervorragenden Antworten eine explizite Implementierung erforderlich, damit der Compiler herausfinden kann, was erforderlich ist. Werfen Sie einen Blick auf IEnumerable<T> als ein Paradebeispiel, das wahrscheinlich ziemlich oft auftauchen wird.

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 warten Sie, sowohl die generische als auch die normale Version beide implementieren Funktionen mit derselben Methodensignatur (C # ignoriert den Rückgabetyp hierfür). Das ist völlig legal und in Ordnung. Wie löst der Compiler die zu verwendende auf? Es zwingt Sie, höchstens eine implizite Definition zu haben, dann kann es auflösen, was immer 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 wird der Funktionsaufruf aufgelöst. Es scheint, als hätten sich für die Implementierung einiger Abstraktionsebenen einige der .NET-Kernschnittstellen angesammelt.

65

Grund # 1

Ich neige dazu, eine explizite Schnittstellenimplementierung zu verwenden, wenn ich "Programmierung zu einer Implementierung" verhindern möchte (Entwurfsprinzipien aus Entwurfsmustern) .

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

Nun ist es weniger wahrscheinlich, dass eine andere Klasse (z. B. Presenter ) von der StandardNavigator-Implementierung abhängt, als vielmehr von der INavigator-Schnittstelle (da die Implementierung in eine Schnittstelle umgewandelt werden müsste, um sie zu verwenden) die Umleitungsmethode).

Grund # 2

Ein weiterer Grund, warum ich mich für eine explizite Schnittstellenimplementierung entscheiden könnte, wäre, die "Standard" -Schnittstelle einer Klasse sauberer zu halten. Wenn ich beispielsweise ein ASP.NET Serversteuerelement entwickle, 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 und die ich für die Steuerung der Steuerlogik entwickle

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 in der Lage sein, einen Kunden durch GUID oder durch Abrufen der GUID des ausgewählten Kunden auszuwählen. Ein Präsentator würde das Feld beim Laden der ersten Seite füllen, und dieser Präsentator wird von der gekapselt Steuerung.

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 mit Daten, und der Webseitenentwickler muss sich nie seiner Existenz 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.

31
Jon Nadal

Um Jeffrey Richter von CLR über C # zu zitieren
( [~ # ~] eimi [~ # ~] bedeutet [~ # ~] e [~ # ~] xplicit [~ # ~] i [~ # ~] nterface [~ # ~] m [~ # ~] Methode [~ # ~] i [~ # ~] Implementierung)

Es ist von entscheidender Bedeutung, dass Sie einige Konsequenzen kennen, die bei der Verwendung von EIMIs auftreten. Und wegen dieser Konsequenzen sollten Sie versuchen, EIMIs so weit wie möglich zu vermeiden. Glücklicherweise helfen Ihnen generische Schnittstellen dabei, EIMIs zu vermeiden. Es kann jedoch vorkommen, dass Sie sie noch benötigen (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 umrahmt.
  • Ein EIMI kann nicht von einem abgeleiteten Typ aufgerufen werden.

Wenn Sie eine Schnittstellenreferenz verwenden, kann JEDE virtuelle Kette in einer abgeleiteten 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 Schnittstellenmember vor grundlegenden Implementierungen von Framework-Schnittstellen wie IEnumerable <T> auszublenden, sodass Ihre Klasse keine nicht stark typisierte Methode direkt verfügbar macht, sondern syntaktisch korrekt ist.

30
Valentin Kuzub

Zusätzlich zu den anderen bereits genannten Gründen implementiert eine Klasse zwei verschiedene Schnittstellen, 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.

Natürlich möchten wir, dass der Wert von Title davon abhängt, ob Class1 als Buch oder als Person behandelt wird. 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 davon ausgegangen wird, dass die expliziten Schnittstellendefinitionen öffentlich sind. Daher können Sie sie nicht explizit als öffentlich (oder anderweitig) deklarieren.

Beachten Sie auch, dass Sie weiterhin eine "gemeinsam genutzte" Version haben können (wie oben gezeigt), aber 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 "freigegebenen" (impliziten) Titel nicht definieren, setzen die Konsumenten von Class1 müssen Instanzen von Class1 explizit zuerst auf IBook oder IPerson um. Andernfalls wird der Code nicht kompiliert.

19
Lee Oades

Ich verwende die meiste Zeit 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, wobei eine vorhandene Klasse, die diese Schnittstelle implementiert, zufällig 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 beim Debuggen schwer zu "sehen", da sich diese Funktion wahrscheinlich nicht in den anderen Schnittstellenmethoden in der Datei befindet (das unten erwähnte selbstdokumentierende Problem).

  • Eine Funktion von einer Schnittstelle entfernen. Implizit implementierte Methoden sind plötzlich toter Code, aber explizit implementierte Methoden werden durch einen Kompilierungsfehler abgefangen. Auch wenn es gut ist, den toten Code aufzubewahren, 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 Prüfungen durchführen kann. Virtuelle Methoden weisen keines der oben genannten Probleme auf, da 'override' und 'new' erforderlich sind.

Hinweis: Für feste oder sich selten ändernde 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 in einer Klasse 'public bool Execute ()' sehe, ist zusätzliche Arbeit erforderlich, um herauszufinden, ob es sich um einen Teil einer Schnittstelle handelt. Jemand muss es wahrscheinlich mit diesen Worten kommentieren oder in eine Gruppe anderer Schnittstellenimplementierungen einfügen, die alle unter einem Regions- oder Gruppenkommentar mit den Worten "Implementierung von ITask" stehen. Dies funktioniert natürlich nur, wenn der Gruppenkopf nicht außerhalb des Bildschirms angezeigt wird.

In Erwägung nachstehender Gründe: 'bool ITask.Execute ()' ist klar und eindeutig.

Klare Trennung der Schnittstellenimplementierung

Ich stelle mir Schnittstellen eher als "öffentlich" als als öffentliche Methoden vor, da sie so gestaltet sind, dass sie nur einen kleinen Teil der Oberfläche des Betontyps offen legen. 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.

Während ich den Code einer Klasse durchschaue, wechselt mein Gehirn in den "Code Contract" -Modus, wenn ich auf explizite Schnittstellenimplementierungen stoße. Oft leiten diese Implementierungen einfach an andere Methoden weiter, aber manchmal führen sie zusätzliche Status-/Parameterprüfungen, die Konvertierung eingehender Parameter zur besseren Anpassung an interne Anforderungen oder sogar die Übersetzung für Versionszwecke durch (d. H. Mehrere Generationen von Schnittstellen, die alle auf gemeinsame Implementierungen beschränkt sind).

(Mir ist klar, dass Publics 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 nur internen Code ist.)

Verwandte: 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 Implikationen halte:

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

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

  1. Fügen Sie publics hinzu und lassen Sie die Schnittstellenmethoden für die Implementierung an diese weiterleiten. Dies geschieht normalerweise mit einfacheren Schnittstellen, wenn Sie intern arbeiten.
  2. (Meine bevorzugte Methode) Fügen Sie einen public IMyInterface I { get { return this; } } (Der inline werden sollte) hinzu und rufen Sie foo.I.InterfaceMethod() auf. Wenn mehrere Schnittstellen diese Fähigkeit benötigen, erweitern Sie den Namen über I hinaus (meiner Erfahrung nach ist es selten, dass ich diesen Bedarf habe).
14
scobi

Wenn Sie explizit implementieren, können Sie die Schnittstellenelemente nur über eine Referenz referenzieren, die dem Typ der Schnittstelle entspricht. Ein Verweis, der dem Typ der implementierenden Klasse entspricht, macht diese Schnittstellenelemente nicht verfügbar.

Wenn Ihre implementierende Klasse nicht öffentlich ist, abgesehen von der Methode, mit der die Klasse erstellt wurde (dies kann eine Factory oder IoC Container sein), und abgesehen von den Schnittstellenmethoden (natürlich), dann gebe ich keine Es wird kein Vorteil gesehen, Schnittstellen explizit zu implementieren.

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. "Stellt sicher", nehme ich an, ist der "Vorteil". Eine gut funktionierende Implementierung kann dies ohne explizite Implementierung erreichen.

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

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.

8
Bill

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

6
Yochai Timmer

Jedes Klassenmitglied, das eine Schnittstelle implementiert, exportiert eine Deklaration, die semantisch der Art und Weise ähnlich ist, wie VB.NET-Schnittstellendeklarationen geschrieben werden, 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 können die Klasse und ihre Derivate unter dem Namen IFoo_Foo Auf ein Klassenmitglied zugreifen, aber die Außenwelt kann nur auf dieses bestimmte Mitglied zugreifen, indem sie das Casting auf IFoo ausführt. Ein solcher Ansatz ist häufig in Fällen sinnvoll, in denen eine Schnittstellenmethode angegeben Verhalten bei allen Implementierungen, aber nützlich Verhalten nur bei einigen [z. Das angegebene Verhalten für die Methode IList<T>.Add einer schreibgeschützten Sammlung besteht darin, NotSupportedException] auszulösen. Leider ist die einzige Möglichkeit, die Schnittstelle in C # zu implementieren, folgende:

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

Nicht so schön.

5
supercat

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

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

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

1
nrodic