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?
Antworten:
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.CopyTo
würde implizit implementiert werden als:und explizit als:
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.
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.
quelle
public
Schlüsselwort hat ... sonst erhalten Sie eine FehlermeldungUISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }
.IEnumerator<T>.Current
,IEnumerable<T>.GetEnumerator()
undISet<T>.Add(T)
. Dies wird in einer anderen Antwort erwähnt .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.
quelle
Cat
es tatsächlichIEatable
keine Einwände gibt.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:
Hier
IEnumerable<string>
implementiertIEnumerable
, 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.
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.
quelle
abstract
.Jeffrey Richter von CLR via C # zu zitieren
( EIMI bedeutet E xplicit I nterface M ethode I UMSETZUNG)
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.
quelle
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:
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:
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.
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.
quelle
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.
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.
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.
quelle
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:
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:
public IMyInterface I { get { return this; } }
(das inline werden sollte) hinzu und rufen Sie auffoo.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).quelle
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.
quelle
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.
MSDN: implizite und explizite Schnittstellenimplementierungen
quelle
Jedes Klassenmitglied, das eine Schnittstelle implementiert, exportiert eine Deklaration, die der Art und Weise, wie VB.NET-Schnittstellendeklarationen geschrieben werden, semantisch ähnlich ist, z
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:
In diesem Fall
IFoo_Foo
kö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 zugreifenIFoo
. 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 dieIList<T>.Add
Methode einer schreibgeschützten Sammlung das AuslösenNotSupportedException
]. Leider ist der einzig richtige Weg, um die Schnittstelle in C # zu implementieren ,:Nicht so schön.
quelle
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.
quelle
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
public
ist, die implementierende Klasse jedochpublic
.Die obige Leckage ist unvermeidbar, da gemäß der C # -Spezifikation "alle Schnittstellenmitglieder implizit öffentlichen Zugriff haben". Infolgedessen müssen implizite Implementierungen auch
public
Zugriff gewähren , selbst wenn die Schnittstelle selbst zinternal
.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 .
quelle