Ist es zulässig, explizite Schnittstellenimplementierung zu verwenden, um Member in C # auszublenden?

14

Ich verstehe, wie man mit Interfaces und der expliziten Implementierung von Interfaces in C # arbeitet, aber ich habe mich gefragt, ob es als schlechte Form angesehen wird, bestimmte Member zu verstecken, die nicht häufig verwendet werden. Beispielsweise:

public interface IMyInterface
{
    int SomeValue { get; set; }
    int AnotherValue { get; set; }
    bool SomeFlag { get; set; }

    event EventHandler SomeValueChanged;
    event EventHandler AnotherValueChanged;
    event EventHandler SomeFlagChanged;
}

Ich weiß im Voraus, dass die Ereignisse zwar nützlich, aber sehr selten genutzt werden würden. Daher hielt ich es für sinnvoll, sie durch explizite Implementierung vor einer implementierenden Klasse zu verbergen, um IntelliSense-Unordnung zu vermeiden.

Kyle Baran
quelle
3
Wenn Sie Ihre Instanz über die Schnittstelle referenzieren, werden sie ohnehin nicht ausgeblendet, sondern nur, wenn Sie auf einen konkreten Typ verweisen.
Andy
1
Ich habe mit einem Typ zusammengearbeitet, der das regelmäßig gemacht hat. Es hat mich total verrückt gemacht.
Joel Etherton
... und beginnen Sie mit der Aggregation.
Mikalai

Antworten:

39

Ja, es ist im Allgemeinen eine schlechte Form.

Daher hielt ich es für sinnvoll, sie durch explizite Implementierung vor einer implementierenden Klasse zu verbergen, um IntelliSense-Unordnung zu vermeiden.

Wenn Ihr IntelliSense zu überladen ist, ist Ihre Klasse zu groß. Wenn Ihre Klasse zu groß ist, macht sie wahrscheinlich zu viele Dinge und / oder ist schlecht gestaltet.

Beheben Sie Ihr Problem, nicht Ihr Symptom.

Telastyn
quelle
Ist es angemessen, Compiler-Warnungen zu nicht verwendeten Mitgliedern zu entfernen?
Gusdor
11
"Beheben Sie Ihr Problem, nicht Ihr Symptom." +1
FreeAsInBeer
11

Verwenden Sie die Funktion für das, wofür sie vorgesehen war. Das Reduzieren von Namespace-Unordnung ist keine davon.

Bei legitimen Gründen geht es nicht darum, sich zu "verstecken" oder sich zu organisieren, sondern Mehrdeutigkeiten aufzulösen und sich zu spezialisieren. Wenn Sie zwei Schnittstellen implementieren, die "Foo" definieren, kann sich die Semantik für Foo unterscheiden. Wirklich kein besserer Weg, um dies zu lösen, außer für explizite Schnittstellen. Die Alternative besteht darin, das Implementieren überlappender Schnittstellen zu verbieten oder zu deklarieren, dass Schnittstellen keine Semantik zuordnen können (was ohnehin nur eine konzeptionelle Sache ist). Beides sind schlechte Alternativen. Manchmal übertrifft die Zweckmäßigkeit die Eleganz.

Einige Autoren / Experten betrachten es als einen Trick eines Features (die Methoden sind nicht wirklich Teil des Objektmodells des Typs). Aber wie alle Funktionen, irgendwo braucht es jemand.

Auch hier würde ich es nicht zum Verstecken oder Organisieren verwenden. Es gibt Möglichkeiten, Mitglieder vor Intellisense zu verbergen.

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
Codenheim
quelle
"Es gibt prominente Autoren / Experten, die es für einen Trick einer Funktion halten." Wer ist? Was ist die vorgeschlagene Alternative? Es gibt mindestens ein Problem (dh die Spezialisierung von Schnittstellenmethoden in einer Subschnittstelle / Implementierungsklasse), bei dem ein anderes Feature zu C # hinzugefügt werden muss, um es zu lösen, wenn keine explizite Implementierung vorliegt (siehe ADO.NET und generische Auflistungen). .
JHOMINAL
@jhominal - CLR über C # von Jeffrey Richter verwendet speziell das Wort kludge. C ++ kommt ohne aus, der Programmierer muss explizit auf die Implementierung der Basismethode verweisen, um eine eindeutige Unterscheidung zu treffen, oder eine Umwandlung verwenden. Ich habe bereits erwähnt, dass die Alternativen nicht sehr attraktiv sind. Aber es ist ein Aspekt der einzelnen Vererbung und Schnittstellen, nicht OOP im Allgemeinen.
Codenheim
Sie hätten auch erwähnen können, dass Java auch keine explizite Implementierung hat, obwohl es dasselbe Design "einzelne Vererbung von Implementierung + Schnittstellen" hat. In Java kann der Rückgabetyp einer Überschreibungsmethode jedoch spezialisiert werden, wodurch ein Teil der Notwendigkeit einer expliziten Implementierung entfällt. (In C # wurde eine andere Entwurfsentscheidung getroffen, wahrscheinlich teilweise aufgrund der Einbeziehung von Eigenschaften).
JHOMINAL
@ jhominal - Klar, wenn ich ein paar Minuten später habe, werde ich updaten. Vielen Dank.
Codenheim
In Situationen, in denen eine Klasse eine Schnittstellenmethode möglicherweise auf eine Weise implementiert, die dem Schnittstellenvertrag entspricht, aber nicht nützlich ist, kann das Ausblenden durchaus angemessen sein . Während ich die Idee von Klassen, in denen IDisposable.Disposeetwas ausgeführt wird, aber ein anderer Name zugewiesen wird Close, nicht mag , würde ich es beispielsweise für eine Klasse als angemessen erachten, deren Vertrag ausdrücklich besagt, dass Code Instanzen erstellen und aufgeben kann, ohne die Disposeexplizite Schnittstellenimplementierung für zu verwenden Definieren Sie eine IDisposable.DisposeMethode, die nichts tut.
Supercat
5

Ich mag ein Zeichen von chaotischem Code sein, aber manchmal ist die reale Welt chaotisch. Ein Beispiel aus .NET Framework ist ReadOnlyColection, das den mutierenden Setter [], Add und Set verbirgt.

IE ReadOnlyCollection implementiert IList <T>. Siehe auch meine Frage an SO vor einigen Jahren, als ich darüber nachdachte.

Esben Skov Pedersen
quelle
Nun, es ist meine eigene Benutzeroberfläche, die ich auch verwenden werde. Es macht also keinen Sinn, es von Anfang an falsch zu machen.
Kyle Baran
2
Ich würde nicht sagen, dass es den mutierenden Setter versteckt, sondern dass es ihn überhaupt nicht bietet. Ein besseres Beispiel wäre ConcurrentDictionary, verschiedene Member von IDictionary<T>explizit so zu implementieren , dass Konsumenten von ConcurrentDictionaryA) sie nicht direkt aufrufen und B) Methoden verwenden können, die eine Implementierung erfordern, IDictionary<T>falls dies unbedingt erforderlich ist. Zum Beispiel der Nutzer ConcurrentDictionary<T> sollten anrufen , TryAddanstatt Addunnötige Ausnahmen notwendig zu vermeiden.
Brian
Natürlich wird durch die Addexplizite Implementierung auch das Durcheinander verringert. TryAddkann alles, was Addkann ( Addwird als Aufruf von implementiert TryAdd, vorausgesetzt, beide wären redundant). Hat jedoch TryAddnotwendigerweise einen anderen Namen / eine andere Signatur, so ist eine Implementierung IDictionaryohne diese Redundanz nicht möglich . Die explizite Implementierung behebt dieses Problem auf saubere Weise.
Brian
Nun, aus Verbrauchersicht sind die Methoden verborgen.
Esben Skov Pedersen
1
Sie schreiben über IReadonlyCollection <T>, verknüpfen jedoch mit ReadOnlyCollection <T>. Der erste in einer Schnittstelle, der zweite ist eine Klasse. IReadonlyCollection <T> implementiert IList <T> nicht, obwohl ReadonlyCollection <T> dies tut.
Jørgen Fogh
2

Es ist eine gute Form, dies zu tun, wenn das Mitglied im Kontext sinnlos ist. Wenn Sie beispielsweise eine schreibgeschützte Auflistung erstellen, die IList<T>durch Delegieren an ein internes Objekt implementiert wird, _wrappedhaben Sie möglicherweise Folgendes :

public T this[int index]
{
  get
  {
     return _wrapped[index];
  }
}
T IList<T>.this[int index]
{
  get
  {
    return this[index];
  }
  set
  {
    throw new NotSupportedException("Collection is read-only.");
  }
}
public int Count
{
  get { return _wrapped.Count; }
}
bool ICollection<T>.IsReadOnly
{
  get
  {
    return true;
  }
}

Hier haben wir vier verschiedene Fälle.

public T this[int index]wird eher von unserer Klasse als von der Schnittstelle definiert und ist daher natürlich keine explizite Implementierung. Beachten Sie jedoch, dass es dem T this[int index]in der Schnittstelle definierten Lese- / Schreibzugriff ähnelt, jedoch schreibgeschützt ist.

T IList<T>.this[int index]ist explizit, weil ein Teil davon (der Getter) perfekt mit der obigen Eigenschaft übereinstimmt und der andere Teil immer eine Ausnahme auslöst. Während dies für jemanden wichtig ist, der über die Schnittstelle auf eine Instanz dieser Klasse zugreift, ist es für jemanden, der sie über eine Variable des Klassentyps verwendet, sinnlos.

Da bool ICollection<T>.IsReadOnlyimmer true zurückgegeben wird, ist es völlig sinnlos, Code zu verwenden, der für den Typ der Klasse geschrieben wurde, kann jedoch für die Verwendung über den Typ der Schnittstelle von entscheidender Bedeutung sein. Daher implementieren wir ihn explizit.

Wird dagegen public int Countnicht explizit implementiert, da es möglicherweise für jemanden von Nutzen sein kann, der eine Instanz über ihren eigenen Typ verwendet.

Aber mit Ihrem "sehr selten verwendeten" Fall würde ich mich sehr stark dafür einsetzen, keine explizite Implementierung zu verwenden.

In den Fällen, in denen ich die Verwendung einer expliziten Implementierung empfehle, die die Methode über eine Variable des Klassentyps aufruft, wäre dies entweder ein Fehler (Versuch, den indizierten Setter zu verwenden) oder sinnlos (Überprüfung eines Werts, der immer derselbe ist) Sie schützen den Benutzer vor fehlerhaftem oder nicht optimalem Code. Dies unterscheidet sich erheblich von Code, von dem Sie glauben, dass er nur selten verwendet wird. Dafür könnte ich erwägen, das EditorBrowsableAttribut zu verwenden, um das Mitglied vor dem Verstand zu verbergen, obwohl selbst ich es müde wäre; Die Gehirne der Menschen haben bereits eine eigene Software, mit der sie herausfiltern können, was sie nicht interessiert.

Jon Hanna
quelle
Wenn Sie eine Schnittstelle implementieren , implementieren Sie die Schnittstelle. Andernfalls liegt ein klarer Verstoß gegen das Liskov-Substitutionsprinzip vor.
Telastyn
@Telastyn das Interface ist ja implementiert.
Jon Hanna
Der Vertrag ist jedoch nicht erfüllt - insbesondere "Dies ist etwas, das eine veränderbare Sammlung mit wahlfreiem Zugriff darstellt".
Telastyn
1
@Telastyn erfüllt den dokumentierten Vertrag, insbesondere im Hinblick auf "Eine schreibgeschützte Sammlung erlaubt das Hinzufügen, Entfernen oder Ändern von Elementen nach dem Erstellen der Sammlung nicht." Siehe msdn.microsoft.com/en-us/library/0cfatk9t%28v=vs.110%29.aspx
Jon Hanna
@Telastyn: Wenn der Vertrag Wandlungsfähigkeit enthalten würde, warum würde die Schnittstelle dann eine IsReadOnlyEigenschaft enthalten ?
Nikie