Menu

C# Interface: deklarieren, implementieren, Standardmethoden und Beispiele

Wie Interfaces in C# funktionieren: eines deklarieren, es in Klassen und Structs implementieren, das Interface als Typ verwenden, mehrere gleichzeitig implementieren, explizite Implementierung, die Framework-Interfaces, die du am häufigsten implementierst (IComparable<T>, IEnumerable<T>, IDisposable), und Standard-Interface-Methoden aus C# 8.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Ein Interface ist ein Vertrag: eine benannte Liste von Membern, die ein Typ zu liefern verspricht. Es sagt, was ein Typ kann, und nichts darüber, wie. Klassen und Structs implementieren Interfaces, und Code, der gegen das Interface geschrieben ist, funktioniert mit jeder Implementierung, auch mit später geschriebenen.

Ein Interface deklarieren und implementieren

Ein Interface deklariert Member ohne Rumpf. Per Konvention beginnt sein Name mit I. Eine Klasse implementiert es, indem sie es nach einem Doppelpunkt nennt und für jeden Member einen öffentlichen Member liefert:

Ausgabe:

15% off: 68
10 off: 70
Best for 80: 68
Best for 40: 30

BestPrice weiß nichts von PercentOff oder FixedOff. Es hängt nur von IDiscount ab, eine später hinzugefügte Klasse BuyOneGetOne braucht also keine Änderung daran. Diese Entkopplung ist der ganze Grund, warum es Interfaces gibt.

Ein paar Regeln zur Deklaration:

  • Interface-Member sind standardmäßig öffentlich, und die implementierenden Member müssen ebenfalls öffentlich sein (außer bei expliziter Implementierung, siehe unten).
  • Ein Interface kann Methoden, Properties, Indexer und Events deklarieren. Es kann keine Instanzfelder oder Konstruktoren deklarieren.
  • Eine Klasse, die einen Member weglässt, kompiliert nicht: Fehler CS0535, „'FixedOff' does not implement interface member 'IDiscount.Apply(decimal)'“.
  • Ein Interface lässt sich nicht mit new erzeugen. Du erzeugst eine implementierende Klasse und kannst sie in einer Variable des Interface-Typs halten: IDiscount d = new FixedOff(5);.

Das Interface als Typ

Eine Variable, ein Parameter, ein Feld oder ein Rückgabetyp kann als Interface deklariert werden. Er nimmt dann jedes Objekt an, das es implementiert, und stellt nur die Member des Interfaces bereit. So schreibst du Code, dem es egal ist, welche Implementierung er bekommt, und so setzen Tests eine Attrappe ein:

public class Checkout
{
    private readonly IPaymentGateway gateway;          // not StripeGateway, not PayPalGateway
    public Checkout(IPaymentGateway gateway) { this.gateway = gateway; }

    public bool Pay(decimal amount) => gateway.Charge(amount);
}

// Production: new Checkout(new StripeGateway(apiKey))
// Unit test:  new Checkout(new FakeGateway(alwaysSucceeds: true))

Die Dependency-Injection-Container in ASP.NET Core bauen darauf auf: Dienste werden über ihr Interface registriert und angefordert.

Mehrere Interfaces implementieren

Eine Klasse hat höchstens eine Basisklasse, kann aber beliebig viele Interfaces implementieren. Zuerst die Basisklasse, dann die Interfaces, getrennt durch Kommas:

Ausgabe:

INVOICE Studio rent March: 950.00
True
True

Interfaces können auch von anderen Interfaces erben: interface IRepository<T> : IReadRepository<T> fügt den geerbten Membern weitere hinzu, und eine Klasse, die IRepository<T> implementiert, muss beide Mengen liefern.

Explizite Interface-Implementierung

Manchmal deklarieren zwei Interfaces einen Member mit demselben Namen und unterschiedlicher Bedeutung, oder ein Interface-Member ergibt auf der öffentlichen Oberfläche der Klasse keinen Sinn. Implementiere ihn explizit, indem du den Interface-Namen voranstellst und den Zugriffsmodifizierer weglässt:

Ausgabe:

Desk lamp,34.90
Desk lamp for 34.90

Ein explizit implementierter Member ist nur über das Interface erreichbar. Das Framework nutzt das, um häufige Typen übersichtlich zu halten: Arrays implementieren IList.Add explizit (es wirft NotSupportedException, da Arrays eine feste Größe haben), deshalb taucht die Methode nicht auf, wenn du array. tippst.

Framework-Interfaces, die du implementieren wirst

Wer ein Standard-Interface implementiert, klinkt seinen Typ in vorhandenen Framework-Code ein. Drei davon begegnen dir ständig.

IComparable<T> gibt einem Typ eine natürliche Ordnung, die List<T>.Sort(), Array.Sort und Max() verwenden. CompareTo gibt eine negative Zahl, null oder eine positive Zahl zurück:

Ausgabe:

1.4, 2.9, 2.10, 10.0

Dieselben Versionen als Strings zu sortieren ergäbe 1.4, 10.0, 2.10, 2.9. Ohne IComparable<T> wirft Sort() eine InvalidOperationException, weil es keine Möglichkeit hat, zwei Version-Objekte zu vergleichen.

IEnumerable<T> macht einen Typ in foreach und mit LINQ verwendbar. Der einfache Weg, GetEnumerator zu implementieren, ist yield return, behandelt auf der Seite zu IEnumerable und yield.

IDisposable kennzeichnet einen Typ, der etwas hält, das freigegeben werden muss (ein Dateihandle, eine Verbindung, einen Timer). Seine eine Methode, Dispose, ruft die using-Anweisung auf, wenn der Block endet, auch wenn eine Exception geworfen wird:

Ausgabe:

open sales.txt
  write to sales.txt: March total: 12400
close sales.txt
after using

Standard-Interface-Methoden (C# 8)

Einem veröffentlichten Interface einen Member hinzuzufügen hat früher jede Klasse gebrochen, die es implementierte. Seit C# 8 kann ein Interface-Member einen Rumpf haben, den implementierende Klassen erben, außer sie liefern einen eigenen:

public interface ILogger
{
    void Write(string message);

    // New in version 2 of the library. Existing implementers keep compiling.
    void Error(string message) => Write("ERROR: " + message);
}

public class ConsoleLogger : ILogger
{
    public void Write(string message) => Console.WriteLine(message);
}

ILogger log = new ConsoleLogger();
log.Error("disk full");            // ERROR: disk full

var direct = new ConsoleLogger();
// direct.Error("x");              // does not compile: the default method belongs to the interface

Die letzte Zeile ist der Teil, der Leute überrascht: Eine Standardmethode wird nicht in die eigenen Member der Klasse übernommen, sie ist also nur über den Interface-Typ aufrufbar. Standardmethoden können außerdem keine Instanzfelder verwenden (Interfaces haben keine); sie arbeiten über die anderen Interface-Member. Sie erfordern .NET Core 3.0 oder neuer und sind unter .NET Framework nicht verfügbar.

C# 8 hat außerdem statische Member in Interfaces erlaubt, und C# 11 hat statische abstrakte Member hinzugefügt, mit denen generischer Code eine statische Methode oder einen Operator auf T aufrufen kann. Darauf baut generische Mathematik in .NET 7 auf (INumber<T>, where T : INumber<T>).

Interface oder abstrakte Klasse?

Nimm ein Interface für eine Fähigkeit, die voneinander unabhängige Typen teilen können, wenn Structs mitmachen sollen oder wenn eine Klasse mehrere solcher Rollen braucht. Nimm eine abstrakte Klasse, wenn die Implementierungen Varianten einer Sache sind und Zustand oder einen festen Algorithmus teilen. Die Seite zu abstrakten Klassen hat eine Gegenüberstellung als Tabelle. Viele Designs verwenden beides: ein Interface für Verbraucher und eine abstrakte Basisklasse, die die langweiligen Teile für Implementierer erledigt.

Häufige Fehler

  • Fehlende oder nicht öffentliche Implementierung. Jeder Interface-Member braucht einen öffentlichen Member mit passender Signatur oder eine explizite Implementierung. Sonst CS0535 (oder CS0737, wenn die Methode existiert, aber nicht öffentlich ist).
  • Interfaces mit einer einzigen Implementierung „für alle Fälle“. Ein Interface verdient seinen Platz, wenn es mehrere Implementierungen oder eine Test-Attrappe gibt. Sonst ist es nur eine weitere Datei zum Navigieren.
  • Aufgeblähte Interfaces. Ein Interface mit zwanzig Membern zwingt jeden Implementierer, zwanzig Member zu schreiben. Teile es in kleinere Rollen auf (IReader, IWriter), die Klassen kombinieren.
  • Ein Interface zurück in eine Klasse casten. ((StripeGateway)gateway).Refund() macht die Entkopplung rückgängig. Wenn Aufrufer Refund brauchen, gehört es ins Interface.
  • Erwarten, eine Standard-Interface-Methode über die Klasse aufzurufen. Sie wird über den Interface-Typ erreicht.

Häufig gestellte Fragen

Was ist ein Interface in C#?

Ein Interface ist eine benannte Menge von Membern (Methoden, Properties, Events, Indexer), die ein Typ zu liefern verspricht, ohne Instanzzustand. interface IDiscount { decimal Apply(decimal price); } sagt „alles, was ein IDiscount ist, kann sich auf einen Preis anwenden“. Klassen und Structs implementieren es mit einem Doppelpunkt, und Code kann dann über den Interface-Typ mit jeder von ihnen arbeiten.

Wie implementiere ich in C# ein Interface?

Nenne es nach einem Doppelpunkt in der Klassendeklaration (nach der Basisklasse, falls es eine gibt) und liefere für jeden Interface-Member einen öffentlichen Member: class HolidayDiscount : IDiscount { public decimal Apply(decimal price) => price * 0.9m; }. Ein fehlender Member ist Fehler CS0535. Visual Studio und Rider können die Rümpfe per Quick Fix erzeugen.

Kann eine C#-Klasse mehrere Interfaces implementieren?

Ja, so viele wie nötig, getrennt durch Kommas: class Invoice : IPrintable, IComparable<Invoice>, IDisposable. Das ist die Antwort von C# auf Mehrfachvererbung: Eine Klasse hat eine Basisklasse, kann aber viele Rollen spielen. Deklarieren zwei Interfaces denselben Member, kann eine öffentliche Methode beide erfüllen, oder du gibst jedem mit expliziter Implementierung seinen eigenen.

Was ist explizite Interface-Implementierung in C#?

Einen Member mit vorangestelltem Interface-Namen und ohne Zugriffsmodifizierer implementieren: string IExportable.Format() { ... }. Der Member ist dann nur über eine Variable des Interface-Typs aufrufbar, nicht über die Klasse. Das dient dazu, Namenskonflikte zwischen zwei Interfaces aufzulösen und selten verwendete Interface-Member aus der öffentlichen Oberfläche der Klasse herauszuhalten.

Was sind Standard-Interface-Methoden in C#?

Seit C# 8 kann ein Interface-Member einen Rumpf haben: void Error(string m) => Write("ERROR: " + m);. Implementierende Klassen bekommen ihn geschenkt und können einen eigenen liefern. Das Feature existiert, damit Bibliotheksautoren einem veröffentlichten Interface Member hinzufügen können, ohne jeden Implementierer zu brechen. Eine Standardmethode wird über den Interface-Typ erreicht, nicht über die Klasse.

Warum beginnen Interface-Namen in C# mit I?

Das ist die .NET-Namenskonvention (IEnumerable, IDisposable, IComparable<T>): ein großes I, gefolgt von einem Namen in PascalCase, oft einem Adjektiv auf „-able“. Der Compiler verlangt es nicht, aber wer ihr folgt, macht Interface-Typen auf einen Blick erkennbar, besonders in einer Klassendeklaration, in der sich Basisklasse und Interfaces eine Liste teilen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S