Suche…


Eine Schnittstelle implementieren

Eine Schnittstelle wird verwendet, um das Vorhandensein einer Methode in jeder Klasse zu erzwingen, die sie "implementiert". Das Interface wird mit dem Schlüsselwort interface und kann von einer Klasse 'implementiert' werden, indem nach dem Klassennamen : InterfaceName hinzugefügt wird. Eine Klasse kann mehrere Schnittstellen implementieren, indem jede Schnittstelle durch ein Komma getrennt wird.
: InterfaceName, ISecondInterface

public interface INoiseMaker
{
    string MakeNoise();
}

public class Cat : INoiseMaker
{
    public string MakeNoise()
    {
        return "Nyan";
    }
}

public class Dog : INoiseMaker
{
    public string MakeNoise()
    {
        return "Woof";
    }
}

Da sie INoiseMaker implementieren, müssen sowohl cat als auch dog die string MakeNoise() -Methode enthalten und können ohne sie nicht kompiliert werden.

Implementierung mehrerer Schnittstellen

public interface IAnimal 
{
    string Name { get; set; }
}

public interface INoiseMaker
{
    string MakeNoise();
}

public class Cat : IAnimal, INoiseMaker
{
    public Cat() 
    {
        Name = "Cat";
    }

    public string Name { get; set; }

    public string MakeNoise()
    {
        return "Nyan";
    }
}

Explizite Schnittstellenimplementierung

Eine explizite Schnittstellenimplementierung ist erforderlich, wenn Sie mehrere Schnittstellen implementieren, die eine gemeinsame Methode definieren. Abhängig davon, welche Schnittstelle zum Aufrufen der Methode verwendet wird, sind verschiedene Implementierungen erforderlich eine gemeinsame Implementierung ist möglich).

interface IChauffeur 
{
    string Drive();
}

interface IGolfPlayer
{
    string Drive();
}

class GolfingChauffeur : IChauffeur, IGolfPlayer 
{
    public string Drive()
    {
        return "Vroom!";
    }

    string IGolfPlayer.Drive()
    {
        return "Took a swing...";
    }
}


GolfingChauffeur obj = new GolfingChauffeur();
IChauffeur chauffeur = obj;
IGolfPlayer golfer = obj;

Console.WriteLine(obj.Drive()); // Vroom!
Console.WriteLine(chauffeur.Drive()); // Vroom!
Console.WriteLine(golfer.Drive()); // Took a swing...

Die Implementierung kann von keiner anderen Stelle aus aufgerufen werden, es sei denn, Sie verwenden die Schnittstelle:

public class Golfer : IGolfPlayer
{
    string IGolfPlayer.Drive()
    {
        return "Swinging hard...";
    }
    public void Swing()
    {
        Drive(); // Compiler error: No such method
    }
}

Aus diesem Grund kann es vorteilhaft sein, komplexen Implementierungscode einer explizit implementierten Schnittstelle in einer separaten, privaten Methode zu speichern.

Eine explizite Schnittstellenimplementierung kann natürlich nur für Methoden verwendet werden, die für diese Schnittstelle tatsächlich vorhanden sind:

public class ProGolfer : IGolfPlayer
{
    string IGolfPlayer.Swear() // Error
    {
        return "The ball is in the pit";
    }
}

Ebenso führt die Verwendung einer expliziten Schnittstellenimplementierung ohne Deklaration dieser Schnittstelle für die Klasse ebenfalls zu einem Fehler.

Hinweis:

Das explizite Implementieren von Schnittstellen kann auch verwendet werden, um toten Code zu vermeiden. Wenn eine Methode nicht mehr benötigt wird und von der Schnittstelle entfernt wird, beschwert sich der Compiler über jede noch vorhandene Implementierung.

Hinweis:

Programmierer erwarten, dass der Vertrag unabhängig vom Kontext des Typs gleich ist, und die explizite Implementierung sollte beim Aufruf kein anderes Verhalten zeigen. Im Gegensatz zum obigen Beispiel sollten IGolfPlayer.Drive und Drive dasselbe tun, wenn dies möglich ist.

Warum verwenden wir Schnittstellen?

Eine Schnittstelle ist eine Definition eines Vertrages zwischen dem Benutzer der Schnittstelle und der Klasse, die sie implementiert. Eine Möglichkeit, sich eine Schnittstelle vorzustellen, ist die Deklaration, dass ein Objekt bestimmte Funktionen ausführen kann.

Nehmen wir an, wir definieren eine Schnittstelle IShape , um verschiedene Arten von Formen IShape Wir erwarten, dass eine Form einen Bereich hat. IShape definieren wir eine Methode, mit der die Schnittstellenimplementierungen gezwungen werden, ihren Bereich zurückzugeben:

public interface IShape
{
    double ComputeArea();
}

Nehmen wir an, wir haben die folgenden zwei Formen: ein Rectangle und einen Circle

public class Rectangle : IShape
{
    private double length;
    private double width;

    public Rectangle(double length, double width)
    {
        this.length = length;
        this.width = width;
    }

    public double ComputeArea()
    {
        return length * width;
    }
}

public class Circle : IShape
{
    private double radius;

    public Circle(double radius)
    {
        this.radius = radius;
    }

    public double ComputeArea()
    {
        return Math.Pow(radius, 2.0) * Math.PI;
    }
}

Jeder von ihnen hat seine eigene Definition seiner Fläche, aber beide sind Formen. Daher ist es nur logisch, sie als IShape in unserem Programm zu sehen:

private static void Main(string[] args)
{
    var shapes = new List<IShape>() { new Rectangle(5, 10), new Circle(5) };
    ComputeArea(shapes);

    Console.ReadKey();
}

private static void ComputeArea(IEnumerable<IShape> shapes) 
{
    foreach (shape in shapes)
    {
        Console.WriteLine("Area: {0:N}, shape.ComputeArea());
    }
}

// Output:
// Area : 50.00
// Area : 78.54

Schnittstellen-Grundlagen

Die Funktion einer Schnittstelle wird als "Vertrag" der Funktionalität bezeichnet. Das bedeutet, dass Eigenschaften und Methoden deklariert, aber nicht implementiert werden.

So anders als Klassen Interfaces:

  • Kann nicht instanziiert werden
  • Kann keine Funktionalität haben
  • Kann nur Methoden enthalten * (Eigenschaften und Ereignisse sind Methoden intern)
  • Das Erben einer Schnittstelle wird als "Implementieren" bezeichnet.
  • Sie können von einer Klasse erben, Sie können jedoch mehrere Schnittstellen "implementieren"
public interface ICanDoThis{
    void TheThingICanDo();
    int SomeValueProperty { get; set; }
}

Dinge zu beachten:

  • Das Präfix "I" ist eine Namenskonvention für Schnittstellen.
  • Der Funktionsrumpf wird durch ein Semikolon ";" ersetzt.
  • Eigenschaften sind auch zulässig, da sie intern auch Methoden sind
public class MyClass : ICanDoThis {
    public void TheThingICanDo(){
        // do the thing
    }

    public int SomeValueProperty { get; set; }
    public int SomeValueNotImplemtingAnything { get; set; }
}

.

ICanDoThis obj = new MyClass();

// ok
obj.TheThingICanDo();

// ok
obj.SomeValueProperty = 5;

// Error, this member doesn't exist in the interface
obj.SomeValueNotImplemtingAnything = 5;

// in order to access the property in the class you must "down cast" it
((MyClass)obj).SomeValueNotImplemtingAnything = 5; // ok

Dies ist besonders nützlich, wenn Sie mit UI-Frameworks wie WinForms oder WPF arbeiten, da es zwingend erforderlich ist, von einer Basisklasse zu erben, um ein Benutzersteuerelement zu erstellen, und Sie die Möglichkeit verlieren, Abstraktionen über verschiedene Steuerelementtypen zu erstellen. Ein Beispiel? In Kürze:

public class MyTextBlock : TextBlock {
    public void SetText(string str){
        this.Text = str;
    }
}

public class MyButton : Button {
    public void SetText(string str){
        this.Content = str;
    }
}

Das vorgeschlagene Problem ist, dass beide ein Konzept von "Text" enthalten, die Eigenschaftsnamen jedoch unterschiedlich sind. Sie können keine abstrakten Basisklassen erstellen, da sie obligatorisch auf zwei verschiedene Klassen vererbt werden. Eine Schnittstelle kann das erleichtern

public interface ITextControl{
    void SetText(string str);
}

public class MyTextBlock : TextBlock, ITextControl {
    public void SetText(string str){
        this.Text = str;
    }
}

public class MyButton : Button, ITextControl {
    public void SetText(string str){
        this.Content = str;
    }

    public int Clicks { get; set; }
}

Jetzt ist MyButton und MyTextBlock austauschbar.

var controls = new List<ITextControls>{
    new MyTextBlock(),
    new MyButton()
};

foreach(var ctrl in controls){
    ctrl.SetText("This text will be applied to both controls despite them being different");


    // Compiler Error, no such member in interface
    ctrl.Clicks = 0;

    // Runtime Error because 1 class is in fact not a button which makes this cast invalid
    ((MyButton)ctrl).Clicks = 0;


    /* the solution is to check the type first.
    This is usually considered bad practice since
    it's a symptom of poor abstraction */
    var button = ctrl as MyButton;
    if(button != null)
        button.Clicks = 0; // no errors

   
}

Mitglieder mit expliziter Implementierung "ausblenden"

Hassen Sie es nicht, wenn Schnittstellen Ihre Klasse mit zu vielen Mitgliedern verschmutzen, für die Sie sich nicht interessieren? Nun, ich habe eine Lösung bekommen! Explizite Implementierungen

public interface IMessageService {
    void OnMessageRecieve();
    void SendMessage();
    string Result { get; set; }
    int Encoding { get; set; }
    // yadda yadda
}

Normalerweise würden Sie die Klasse so implementieren.

public class MyObjectWithMessages : IMessageService {
     public void OnMessageRecieve(){

     }

     public void SendMessage(){

     }

     public string Result { get; set; }
     public int Encoding { get; set; }
}

Jedes Mitglied ist öffentlich.

var obj = new MyObjectWithMessages();

// why would i want to call this function?
obj.OnMessageRecieve();

Antwort: ich nicht. Es sollte also auch nicht als öffentlich deklariert werden, sondern durch die Angabe der Mitglieder als privat wird der Compiler einen Fehler auslösen

Die Lösung ist die explizite Implementierung:

public class MyObjectWithMessages : IMessageService{
    void IMessageService.OnMessageRecieve() {
        
    }

    void IMessageService.SendMessage() {
        
    }

    string IMessageService.Result { get; set; }
    int IMessageService.Encoding { get; set; }
}

Jetzt haben Sie die Mitglieder nach Bedarf implementiert und sie werden keine Mitglieder als öffentlich ausweisen.

var obj = new MyObjectWithMessages();

/* error member does not exist on type MyObjectWithMessages. 
 * We've succesfully made it "private" */
obj.OnMessageRecieve();

Wenn Sie ernsthaft weiterhin auf das Member zugreifen möchten, obwohl es explizit implementiert ist, müssen Sie das Objekt nur in die Schnittstelle umwandeln und Sie können es tun.

((IMessageService)obj).OnMessageRecieve();

Vergleichbar als Beispiel für die Implementierung einer Schnittstelle

Schnittstellen können abstrakt erscheinen, bis Sie sie in der Praxis erscheinen. IComparable und IComparable<T> sind hervorragende Beispiele dafür, warum Schnittstellen für uns hilfreich sein können.

Nehmen wir an, in einem Programm für einen Online-Shop haben wir verschiedene Artikel, die Sie kaufen können. Jeder Artikel hat einen Namen, eine ID-Nummer und einen Preis.

public class Item {
    
    public string name; // though public variables are generally bad practice,
    public int idNumber; // to keep this example simple we will use them instead
    public decimal price; // of a property.

    // body omitted for brevity        

}

Wir haben unsere Item in einer List<Item> gespeichert, und in unserem Programm möchten wir unsere Liste nach ID-Nummern von klein nach groß sortieren. Anstatt unseren eigenen Sortieralgorithmus zu schreiben, können wir stattdessen die Sort() Methode verwenden, die List<T> bereits hat. Da unsere Item Klasse jedoch jetzt ist, kann die List<T> nachvollziehen, in welcher Reihenfolge die Liste sortiert wird. Hier kommt die IComparable Schnittstelle ins IComparable .

Um die CompareTo Methode korrekt zu implementieren, sollte CompareTo eine positive Zahl zurückgeben, wenn der Parameter "kleiner als" der aktuelle ist, Null, wenn sie gleich sind, und eine negative Zahl, wenn der Parameter "größer als" ist.

Item apple = new Item();
apple.idNumber = 15;
Item banana = new Item();
banana.idNumber = 4;
Item cow = new Item();
cow.idNumber = 15;
Item diamond = new Item();
diamond.idNumber = 18;

Console.WriteLine(apple.CompareTo(banana)); // 11
Console.WriteLine(apple.CompareTo(cow)); // 0
Console.WriteLine(apple.CompareTo(diamond)); // -3

Hier ist das Beispiel Item Implementierung der Schnittstelle s‘:

public class Item : IComparable<Item> {
    
    private string name;
    private int idNumber;
    private decimal price;

    public int CompareTo(Item otherItem) {

        return (this.idNumber - otherItem.idNumber);

    }

    // rest of code omitted for brevity    

}

Auf Oberflächenebene gibt die CompareTo Methode in unserem Artikel einfach die Differenz in ihren ID-Nummern zurück. Was macht das oben in der Praxis?

Nun, wenn wir rufen Sort() auf einer List<Item> Objekt, die List wird die automatisch aufrufen Item ‚s CompareTo Methode , wenn es zu bestimmen , muss in welcher Reihenfolge in Objekte zu setzen. Außerdem neben List<T> , alle andere Objekte Wenn zwei Objekte miteinander verglichen werden müssen, funktioniert das mit dem Item da wir die Fähigkeit definiert haben, zwei verschiedene Item zu vergleichen.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow