Warum Schnittstelle explizit implementieren?


122

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:


146

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()
    {
    }
}

Ja, genau dies ist ein Fall, den EIMI löst. Und andere Punkte werden durch die Antwort "Michael B" abgedeckt.
verschlüsselt

10
Exzellentes Beispiel. Ich liebe die Schnittstellen- / Klassennamen! :-)
Brian Rogers

11
Ich mag es nicht, zwei Methoden mit derselben Signatur in einer Klasse, die sehr unterschiedliche Dinge tun? Dies ist äußerst gefährliches Zeug und wird sehr wahrscheinlich bei jeder großen Entwicklung Chaos verursachen. Wenn Sie Code wie diesen haben, würde ich sagen, dass Ihre Analyse und Ihr Design im Wahzoo sind.
Mick

4
@Mike Die Schnittstellen können zu einer API oder zu zwei verschiedenen APIs gehören. Vielleicht ist die Liebe hier etwas übertrieben, aber ich wäre zumindest froh, dass eine explizite Implementierung verfügbar ist.
TobiMcNamobi

@BrianRogers Und die Methodennamen auch ;-)
Sнаđошƒаӽ

66

Es ist nützlich, das nicht bevorzugte Mitglied auszublenden. Wenn Sie beispielsweise beides implementieren IComparable<T>und IComparablees 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. IConvertibleWenn 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.


1
Sie haben einen Tippfehler in Ihrer Einschränkung. Zum Klären funktioniert der obige Code. Es muss in der Erstdeklaration der Methodensignatur in der Schnittstelle enthalten sein. Der ursprüngliche Beitrag hat dies nicht angegeben. Das richtige Format ist außerdem "void SomeMehtod <T> (T obj), wobei T: IConvertible. Beachten Sie, dass zwischen") "und" where "ein zusätzlicher Doppelpunkt steht, der nicht vorhanden sein sollte. Trotzdem +1 für die geschickte Verwendung von Generika, um teures Boxen zu vermeiden.
Zack Jannsen

1
Hallo Michael B. Warum also bei der Implementierung eines Strings in .NET eine öffentliche Implementierung von IComparable: public int CompareTo (Objektwert) {if (Wert == null) {return 1; } if (! (Wert ist String)) {neue ArgumentException auslösen (Environment.GetResourceString ("Arg_MustBeString")); } return String.Compare (this, (String) value, StringComparison.CurrentCulture); } Vielen Dank!
Zzfima

stringwar 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, stringalso ließen sie es so, wie es mit dem Schutz war.
Michael B

37

Einige zusätzliche Gründe, eine Schnittstelle explizit zu implementieren:

Abwärtskompatibilität : Falls sich die ICloneableSchnittstelle ändert, müssen implementierende Methodenklassenmitglieder ihre Methodensignaturen nicht ändern.

sauberer Code : Wenn die CloneMethode 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 ICloneableexplizite Implementierung ermöglicht eine starke Typisierung,Clone() wenn Sie ihn direkt als MyObjectInstanzmitglied aufrufen :

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

Für diesen würde ich es vorziehen 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 protectedKlonmethode und versiegelte Klassen, die von ihnen abgeleitet sind und das öffentliche Klonen verfügbar machen.
Superkatze

Sicher, das wäre schöner, außer es gibt keine kovariante Version von ICloneable in der BCL, also müssten Sie eine erstellen, oder?
Wiebe Tijsma

Alle drei Beispiele hängen von unwahrscheinlichen Situationen ab und brechen Best Practices für Schnittstellen ab.
MickyD

13

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 CloneMethode muss dennoch einen eigenen Typ zurückgeben.

Ebenso IAutomobileFactorykönnte a eine ManufactureMethode haben, die ein zurückgibt Automobile, aber a FordExplorerFactory, die implementiert IAutomobileFactory, könnte seine ManufactureMethode a zurückgeben lassen FordExplorer(was von abgeleitet ist Automobile). Code, der weiß, dass er eine hat, FordExplorerFactorykönnte FordExplorer-spezifische Eigenschaften für ein Objekt verwenden, das von a zurückgegeben wird, FordExplorerFactoryohne typisiert werden zu müssen, während Code, der lediglich wusste, dass er eine Art von hat, IAutomobileFactoryseine Rückgabe einfach als Automobile.


2
+1 ... dies wäre meine bevorzugte Verwendung expliziter Schnittstellenimplementierungen, obwohl ein kleines Codebeispiel wahrscheinlich etwas klarer wäre als diese Geschichte :)
Wiebe Tijsma

7

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
}

60
Das seltsamste Beispiel im Zusammenhang mit OO, das ich je gesehen habe:public class Animal : Cat, Dog
mbx

38
@mbx: Wenn Animal auch Parrot implementieren würde, wäre es ein Polly-Morphing-Tier.
RenniePet

3
Ich erinnere mich an eine Zeichentrickfigur, die an einem Ende eine Katze und am anderen Ende ein Hund war
;-)

2
In den 80ern gab es eine TV-Serie ... "Manimal" ... in der sich ein Mann in eine ... oh egal
bkwdesign

Morkies sehen irgendwie aus wie Katzen und sind auch wie Ninja-Katzen.
Samis

6

Es kann die öffentliche Schnittstelle sauberer halten, um eine Schnittstelle explizit zu implementieren, dh Ihre FileKlasse kann IDisposableexplizit 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.


Ich denke, dass die meisten Versionen von VB auch nur explizite Schnittstellendefinitionen unterstützen.
Gabe

1
@Gabe - es ist subtiler als das für VB - die Benennung und Zugänglichkeit von Mitgliedern, die eine Schnittstelle implementieren, unterscheidet sich von der Angabe, dass sie Teil der Implementierung sind. Wenn Sie also in VB die Antwort von @ Iain (aktuelle Top-Antwort) betrachten, können Sie IDoItFast und IDoItSlow mit den öffentlichen Mitgliedern "GoFast" bzw. "GoSlow" implementieren.
Damien_The_Unbeliever

2
Ich mag Ihr spezielles Beispiel nicht (IMHO, die einzigen Dinge, die sich verbergen sollten, Disposesind 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.
Supercat

5

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.


Ok, das erklärt, warum das Projekt nicht mit einer impliziten Implementierung kompiliert werden würde.
Pauloya

4

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.


IMHO ist es wichtiger zu entscheiden, ob die Klasse die Methode ihren Clients zugänglich machen soll oder nicht. Das bestimmt, ob explizit oder implizit. Zu dokumentieren, dass mehrere Methoden zusammengehören, in diesem Fall, weil sie einen Vertrag erfüllen - dafür #regionist dies mit einer entsprechenden Titelzeichenfolge vorgesehen. Und ein Kommentar zur Methode.
ToolmakerSteve

1

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 Adddie 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();

0

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

1
"Alle Methoden sind automatisch privat" - dies ist technisch nicht korrekt. Wenn sie tatsächlich privat wären, wären sie überhaupt nicht aufrufbar, Casting oder nicht.
Samis

0

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();    
        }
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.