Freigeben über


Schnittstellen: Definieren des Verhaltens für mehrere Typen

Tipp

Neu bei der Entwicklung von Software? Beginnen Sie zuerst mit den Lernprogrammen " Erste Schritte ". Sobald Sie gemeinsames Verhalten für nicht verwandte Typen definieren müssen, stoßen Sie auf Schnittstellen.

Haben Sie Erfahrung in einer anderen Sprache? C#-Schnittstellen ähneln Schnittstellen in Java oder Protokollen in Swift. Überfliegen Sie den expliziten Implementierungsabschnitt nach C#-spezifischen Mustern.

Eine Schnittstelle definiert einen Vertrag: eine Gruppe verwandter Methoden, Eigenschaften, Ereignisse und Indexer, die ein class oder struct implementieren muss. Schnittstellen ermöglichen es einem einzelnen Typ, mehrere Verträge zu implementieren, was wichtig ist, da C# keine mehrfache Vererbung von Klassen unterstützt. Strukturen können nicht von anderen Strukturen oder Klassen erben, sodass Schnittstellen die einzige Möglichkeit sind, gemeinsames Verhalten über Strukturtypen hinweg hinzuzufügen.

Im folgenden Beispiel wird eine Schnittstelle und eine Klasse deklariert, die sie implementiert:

interface IEquatable<T>
{
    bool Equals(T obj);
}
public class Car : IEquatable<Car>
{
    public string? Make { get; set; }
    public string? Model { get; set; }
    public string? Year { get; set; }

    public bool Equals(Car? car) =>
        car is not null &&
        (Make, Model, Year) == (car.Make, car.Model, car.Year);
}

Jede Klasse oder Struktur, die IEquatable<T> implementiert, muss eine Methode vom Typ Equals bereitstellen, die der Schnittstellensignatur entspricht. Sie können sich auf jede IEquatable<T> Implementierung verlassen, um den Gleichheitsvergleich zu unterstützen, unabhängig vom konkreten Typ. Diese Vorhersagbarkeit ist der Kernwert von Schnittstellen.

Deklarieren einer Schnittstelle

Definieren Sie eine Schnittstelle mit dem interface Schlüsselwort. Standardmäßig beginnen Schnittstellennamen mit einem Großbuchstaben I:

interface ILogger
{
    void Log(string message);
    string Name { get; }
}

Schnittstellen können Methoden, Eigenschaften, Ereignisse und Indexer enthalten. Eine Schnittstelle kann keine Instanzfelder, Instanzkonstruktoren oder FInalizer enthalten. Mitglieder sind public standardmäßig. Sie können bei Bedarf weitere Modifizierer für Barrierefreiheit angeben. Verwenden Sie internal beispielsweise für Mitglieder, die außerhalb der Assembly nicht sichtbar sein sollten.

Implementieren einer Schnittstelle

Eine Klasse oder Struktur listet die Schnittstellen auf, die sie nach einem Doppelpunkt in der Deklaration implementiert. Die Klasse muss eine Implementierung für jedes Element bereitstellen, das in der Schnittstelle deklariert ist:

public class ConsoleLogger : ILogger
{
    public string Name => "Console";

    public void Log(string message) =>
        Console.WriteLine($"[{Name}] {message}");
}

public class FileLogger : ILogger
{
    public string Name => "File";

    public void Log(string message)
    {
        // In a real app, write to a file
        Console.WriteLine($"[{Name}] Writing to file: {message}");
    }
}

Eine Klasse kann mehrere Schnittstellen implementieren, getrennt durch Kommas. Sie muss Implementierungen für alle Member aus allen Schnittstellen bereitstellen, die sie auflistet.

Explizite Implementierung

Manchmal müssen Sie ein Schnittstellenmitglied implementieren, ohne es teil der öffentlichen API der Klasse zu machen. Die explizite Implementierung qualifiziert das Element mit dem Schnittstellennamen. Auf das Element kann nur über eine Variable des Schnittstellentyps zugegriffen werden:

interface IMetric
{
    double GetDistance(); // Returns meters
}

interface IImperial
{
    double GetDistance(); // Returns feet
}

public class Runway(double meters) : IMetric, IImperial
{
    // Explicit implementation for IMetric
    double IMetric.GetDistance() => meters;

    // Explicit implementation for IImperial
    double IImperial.GetDistance() => meters * 3.28084;
}

Die explizite Implementierung ist nützlich, wenn zwei Schnittstellen Member mit demselben Namen deklarieren oder wenn Sie die öffentliche Oberfläche der Klasse sauber halten möchten. Weitere Informationen finden Sie unter Explizite Schnittstellenimplementierung.

Schnittstellenvererbung

Schnittstellen können von einer oder mehreren anderen Schnittstellen erben. Eine Klasse, die eine abgeleitete Schnittstelle implementiert, muss alle Member aus der abgeleiteten Schnittstelle und alle zugehörigen Basisschnittstellen implementieren:

interface IDrawable
{
    void Draw();
}

interface IShape : IDrawable
{
    double Area { get; }
}

public class Circle(double radius) : IShape
{
    public double Area => Math.PI * radius * radius;

    public void Draw() =>
        Console.WriteLine($"Drawing circle with area {Area:F2}");
}

Eine Klasse, die IShape implementiert, kann implizit in IDrawable umgewandelt werden, da IShape von dieser erbt.

Schnittstellen im Vergleich zu abstrakten Klassen

Sowohl Schnittstellen als auch abstrakte Klassen definieren Verträge, die abgeleitete Typen erfüllen müssen.

  • Verwenden Sie eine abstrakte Klasse , wenn verwandte Typen den Status (Felder), Konstruktoren oder nicht öffentliche Member gemeinsam nutzen. Mit abstrakten Klassen können Sie eine Hierarchie weiterentwickeln, indem Sie neue Member mit Standardverhalten hinzufügen, ohne vorhandene abgeleitete Typen zu unterbrechen.
  • Verwenden Sie eine Schnittstelle , wenn ein Typ einen Vertrag erfüllen muss, der sich auf nicht zusammenhängende Hierarchien schneidet oder wenn er mehrere Verträge implementieren muss. Schnittstellen können keine Instanzfelder oder Konstruktoren deklarieren, daher eignen sie sich am besten zum Hinzufügen von Funktionen zu Typen, die bereits über eine Basisklasse verfügen. Für erweiterte Szenarien unterstützen Schnittstellen auch Standardmitgliedsimplementierungen.

Eine Klasse kann nur von einer Basisklasse erben, aber mehrere Schnittstellen implementieren. Diese Unterscheidung macht schnittstellen häufig die bessere Wahl für die Definition von Funktionen, die sich über Typhierarchien erstrecken.

Arbeiten mit internen Schnittstellen

In der Regel können Sie eine interne Schnittstelle mit öffentlichen Mitgliedern implementieren, solange alle Typen in der Schnittstellensignatur öffentlich zugänglich sind. Wenn eine Schnittstelle interne Typen in ihren Membersignaturen verwendet, müssen Sie diese explizit implementieren, da das implementierende Element nicht öffentlich sein kann, während interne Typen offengelegt werden.

internal class InternalConfiguration
{
    public string Setting { get; set; } = "";
}

internal interface ILoggable
{
    void Log(string message);
}

internal interface IConfigurable
{
    void Configure(InternalConfiguration config);
}

public class ServiceImplementation : ILoggable, IConfigurable
{
    // Implicit implementation: ILoggable uses only public types in its signature
    public void Log(string message) =>
        Console.WriteLine($"Log: {message}");

    // Explicit implementation: IConfigurable uses internal types
    void IConfigurable.Configure(InternalConfiguration config) =>
        Console.WriteLine($"Configured with: {config.Setting}");
}

Im vorherigen Beispiel IConfigurable wird ein interner Typ InternalConfiguration in der Methodensignatur verwendet. ServiceImplementation verwendet die explizite Implementierung für dieses Element. Verwendet dagegen ILoggable nur öffentliche Typen (string) in seiner Signatur und kann implizit implementiert werden.

Standard-Schnittstellenmmber und statische abstrakte Member

Schnittstellen unterstützen zwei erweiterte Features, die über grundlegende Verträge hinausgehen:

  • Standard-Schnittstellenmember ermöglichen es einer Schnittstelle, einen Methodenkörper bereitzustellen. Implementierende Typen erben die Standardimplementierung und können sie optional überschreiben. Weitere Informationen finden Sie unter Standardschnittstellenmethoden.
  • Statische abstrakte Member erfordern die Implementierung von Typen, um ein statisches Element bereitzustellen, das zum Definieren von Operatorverträgen oder Factorymustern nützlich ist. Weitere Informationen finden Sie unter statischen abstrakten Elementen in Schnittstellen.

Beide Features werden im Artikel zu Schnittstellen in der Sprachreferenz behandelt. Die meisten alltäglichen Schnittstellennutzungen beinhalten das Deklarieren und Implementieren von Mustern, die weiter oben in diesem Artikel beschrieben wurden.

Zusammenfassung zu Schnittstellen

  • Eine Schnittstelle definiert einen Vertrag von Methoden, Eigenschaften, Ereignissen und Indexern.
  • Eine Klasse oder Struktur, die eine Schnittstelle implementiert, muss Implementierungen für alle deklarierten Member bereitstellen (es sei denn, die Schnittstelle stellt eine Standardimplementierung bereit).
  • Sie können eine Schnittstelle nicht direkt instanziieren.
  • Eine Klasse oder Struktur kann mehrere Schnittstellen implementieren. Eine Klasse kann eine Basisklasse erben und zudem eine oder mehrere Schnittstellen implementieren.
  • Schnittstellennamen beginnen konventionell mit I.