Was genau ist ein guter Anwendungsfall für die explizite Implementierung einer Schnittstelle?
Ist es nur so, dass Benutzer der Klasse nicht alle diese Methoden / Eigenschaften in Intellisense betrachten müssen?
Was genau ist ein guter Anwendungsfall für die explizite Implementierung einer Schnittstelle?
Ist es nur so, dass Benutzer der Klasse nicht alle diese Methoden / Eigenschaften in Intellisense betrachten müssen?
Antworten:
Wenn Sie zwei Schnittstellen implementieren, beide mit derselben Methode und unterschiedlichen Implementierungen, müssen Sie diese explizit implementieren.
public interface IDoItFast
{
void Go();
}
public interface IDoItSlow
{
void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
void IDoItFast.Go()
{
}
void IDoItSlow.Go()
{
}
}
Es ist nützlich, das nicht bevorzugte Mitglied auszublenden. Wenn Sie beispielsweise beides implementieren IComparable<T>
und IComparable
es normalerweise besser ist, die IComparable
Überlastung zu verbergen, um nicht den Eindruck zu erwecken, dass Sie Objekte verschiedener Typen vergleichen können. In ähnlicher Weise sind einige Schnittstellen nicht CLS-kompatibel. IConvertible
Wenn Sie die Schnittstelle also nicht explizit implementieren, können Endbenutzer von Sprachen, die CLS-Konformität erfordern, Ihr Objekt nicht verwenden. (Was sehr katastrophal wäre, wenn die BCL-Implementierer die IConvertible-Mitglieder der Grundelemente nicht verstecken würden :))
Ein weiterer interessanter Hinweis ist, dass die Verwendung eines solchen Konstrukts normalerweise bedeutet, dass Strukturen, die eine Schnittstelle explizit implementieren, diese nur durch Boxen an den Schnittstellentyp aufrufen können. Sie können dies umgehen, indem Sie generische Einschränkungen verwenden:
void SomeMethod<T>(T obj) where T:IConvertible
Boxt kein Int, wenn Sie eines an ihn übergeben.
string
war schon vor Generika und diese Praxis war in Mode. Als .net 2 auf den Markt kam, wollten sie die öffentliche Schnittstelle von .net nicht unterbrechen, string
also ließen sie es so, wie es mit dem Schutz war.
Einige zusätzliche Gründe, eine Schnittstelle explizit zu implementieren:
Abwärtskompatibilität : Falls sich die ICloneable
Schnittstelle ändert, müssen implementierende Methodenklassenmitglieder ihre Methodensignaturen nicht ändern.
sauberer Code : Wenn die Clone
Methode aus ICloneable entfernt wird, tritt ein Compilerfehler auf. Wenn Sie die Methode jedoch implizit implementieren, können nicht verwendete "verwaiste" öffentliche Methoden auftreten
Starke Typisierung : Um die Geschichte von Supercat anhand eines Beispiels zu veranschaulichen, wäre dies mein bevorzugter Beispielcode. Die ICloneable
explizite Implementierung ermöglicht eine starke Typisierung,Clone()
wenn Sie ihn direkt als MyObject
Instanzmitglied aufrufen :
public class MyObject : ICloneable
{
public MyObject Clone()
{
// my cloning logic;
}
object ICloneable.Clone()
{
return this.Clone();
}
}
interface ICloneable<out T> { T Clone(); T self {get;} }
. Beachten Sie, dass es absichtlich keine ICloneable<T>
Einschränkung für T gibt. Während ein Objekt im Allgemeinen nur dann sicher geklont werden kann, wenn seine Basis vorhanden ist, möchte man möglicherweise von einer Basisklasse ableiten, die sicher ein Objekt einer Klasse klonen kann, die dies nicht kann. Um dies zu ermöglichen, würde ich empfehlen, dass vererbbare Klassen keine öffentliche Klonmethode verfügbar machen. Verwenden Sie stattdessen vererbbare Klassen mit einer protected
Klonmethode und versiegelte Klassen, die von ihnen abgeleitet sind und das öffentliche Klonen verfügbar machen.
Eine andere nützliche Technik besteht darin, dass die öffentliche Implementierung einer Methode durch eine Funktion einen Wert zurückgibt, der spezifischer ist als in einer Schnittstelle angegeben.
Beispielsweise kann ein Objekt implementieren ICloneable
, aber seine öffentlich sichtbare Clone
Methode muss dennoch einen eigenen Typ zurückgeben.
Ebenso IAutomobileFactory
könnte a eine Manufacture
Methode haben, die ein zurückgibt Automobile
, aber a FordExplorerFactory
, die implementiert IAutomobileFactory
, könnte seine Manufacture
Methode a zurückgeben lassen FordExplorer
(was von abgeleitet ist Automobile
). Code, der weiß, dass er eine hat, FordExplorerFactory
könnte FordExplorer
-spezifische Eigenschaften für ein Objekt verwenden, das von a zurückgegeben wird, FordExplorerFactory
ohne typisiert werden zu müssen, während Code, der lediglich wusste, dass er eine Art von hat, IAutomobileFactory
seine Rückgabe einfach als Automobile
.
Dies ist auch nützlich, wenn Sie zwei Schnittstellen mit demselben Mitgliedsnamen und derselben Signatur haben, das Verhalten jedoch je nach Verwendung ändern möchten. (Ich empfehle nicht, Code wie diesen zu schreiben):
interface Cat
{
string Name {get;}
}
interface Dog
{
string Name{get;}
}
public class Animal : Cat, Dog
{
string Cat.Name
{
get
{
return "Cat";
}
}
string Dog.Name
{
get
{
return "Dog";
}
}
}
static void Main(string[] args)
{
Animal animal = new Animal();
Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
Dog dog = animal;
Console.WriteLine(cat.Name); //Prints Cat
Console.WriteLine(dog.Name); //Prints Dog
}
public class Animal : Cat, Dog
Es kann die öffentliche Schnittstelle sauberer halten, um eine Schnittstelle explizit zu implementieren, dh Ihre File
Klasse kann IDisposable
explizit implementieren und eine öffentliche Methode bereitstellen, Close()
die für einen Verbraucher möglicherweise sinnvoller ist als Dispose(
).
F # bietet nur eine explizite Schnittstellenimplementierung, sodass Sie immer auf die jeweilige Schnittstelle umstellen müssen, um auf deren Funktionalität zuzugreifen. Dies führt zu einer sehr expliziten (kein Wortspiel beabsichtigten) Verwendung der Schnittstelle.
Dispose
sind diejenigen, die niemals bereinigt werden müssen); Ein besseres Beispiel wäre so etwas wie die Implementierung einer unveränderlichen Sammlung von IList<T>.Add
.
Wenn Sie eine interne Schnittstelle haben und die Mitglieder in Ihrer Klasse nicht öffentlich implementieren möchten, würden Sie sie explizit implementieren. Implizite Implementierungen müssen öffentlich sein.
Ein weiterer Grund für die explizite Implementierung ist die Wartbarkeit .
Wenn eine Klasse "beschäftigt" wird - ja, es kommt vor, wir haben nicht alle den Luxus, den Code anderer Teammitglieder umzugestalten -, macht eine explizite Implementierung deutlich, dass eine Methode vorhanden ist, um einen Schnittstellenvertrag zu erfüllen.
So wird die "Lesbarkeit" des Codes verbessert.
#region
ist dies mit einer entsprechenden Titelzeichenfolge vorgesehen. Und ein Kommentar zur Methode.
Ein anderes Beispiel ist das System.Collections.Immutable
, in dem die Autoren die Technik gewählt haben, um eine vertraute API für Sammlungstypen beizubehalten, während die Teile der Schnittstelle entfernt wurden, die für ihre neuen Typen keine Bedeutung haben.
Konkret ImmutableList<T>
implementiert IList<T>
und somit ICollection<T>
( um eine ImmutableList<T>
einfachere Verwendung mit Legacy-Code zu ermöglichen), void ICollection<T>.Add(T item)
macht jedoch keinen Sinn für a ImmutableList<T>
: Da das Hinzufügen eines Elements zu einer unveränderlichen Liste die vorhandene Liste nicht ändern darf, ImmutableList<T>
leitet sich auch davon ab, für IImmutableList<T>
wen IImmutableList<T> Add(T item)
verwendet werden kann unveränderliche Listen.
Im Fall von sehen Add
die Implementierungen also ImmutableList<T>
wie folgt aus:
public ImmutableList<T> Add(T item)
{
// Create a new list with the added item
}
IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);
void ICollection<T>.Add(T item) => throw new NotSupportedException();
int IList.Add(object value) => throw new NotSupportedException();
Bei explizit definierten Schnittstellen sind alle Methoden automatisch privat. Sie können ihnen keinen öffentlichen Zugriffsmodifikator zuweisen. Annehmen:
interface Iphone{
void Money();
}
interface Ipen{
void Price();
}
class Demo : Iphone, Ipen{
void Iphone.Money(){ //it is private you can't give public
Console.WriteLine("You have no money");
}
void Ipen.Price(){ //it is private you can't give public
Console.WriteLine("You have to paid 3$");
}
}
// So you have to cast to call the method
class Program
{
static void Main(string[] args)
{
Demo d = new Demo();
Iphone i1 = (Iphone)d;
i1.Money();
((Ipen)i1).Price();
Console.ReadKey();
}
}
// You can't call methods by direct class object
So können wir eine explizite Schnittstelle erstellen: Wenn wir zwei Schnittstellen haben und beide Schnittstellen dieselbe Methode haben und eine einzelne Klasse diese beiden Schnittstellen erbt, wurde der Compiler beim Aufrufen einer Schnittstellenmethode verwirrt, welche Methode aufgerufen werden soll Verwalten Sie dieses Problem mithilfe der expliziten Schnittstelle. Hier ist ein Beispiel, das ich unten gegeben habe.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace oops3
{
interface I5
{
void getdata();
}
interface I6
{
void getdata();
}
class MyClass:I5,I6
{
void I5.getdata()
{
Console.WriteLine("I5 getdata called");
}
void I6.getdata()
{
Console.WriteLine("I6 getdata called");
}
static void Main(string[] args)
{
MyClass obj = new MyClass();
((I5)obj).getdata();
Console.ReadLine();
}
}
}