Tutorial: Untermischen von Funktionalität beim Erstellen von Klassen mithilfe von Schnittstellen mit StandardschnittstellenmethodenTutorial: Mix functionality in when creating classes using interfaces with default interface methods

Ab C# 8.0 können Sie in .NET Core 3.0 eine Implementierung definieren, wenn Sie einen Member einer Schnittstelle deklarieren.Beginning with C# 8.0 on .NET Core 3.0, you can define an implementation when you declare a member of an interface. Dieses Feature bietet neue Funktionen, mit denen Sie Standardimplementierungen für Funktionen definieren können, die in Schnittstellen deklariert werden.This feature provides new capabilities where you can define default implementations for features declared in interfaces. Klassen können auswählen, wann die Funktionalität überschrieben werden soll, wann die Standardfunktionalität verwendet werden soll und wann keine Unterstützung für diskrete Features deklariert werden soll.Classes can pick when to override functionality, when to use the default functionality, and when not to declare support for discrete features.

In diesem Tutorial lernen Sie, wie die folgenden Aufgaben ausgeführt werden:In this tutorial, you'll learn how to:

  • Erstellen von Schnittstellen mit Implementierungen, die diskrete Features beschreiben.Create interfaces with implementations that describe discrete features.
  • Erstellen von Klassen, die die Standardimplementierungen verwenden.Create classes that use the default implementations.
  • Erstellen von Klassen, die einige oder alle Standardimplementierungen überschreiben.Create classes that override some or all of the default implementations.

Erforderliche KomponentenPrerequisites

Sie müssen Ihren Computer zur Ausführung von .NET Core einrichten, einschließlich des C# 8.0-Compilers.You’ll need to set up your machine to run .NET Core, including the C# 8.0 compiler. Der C# 8.0-Compiler steht ab Visual Studio 2019, 16.3 Version 16.3. oder mit dem .NET Core 3.0 SDK oder höher zur Verfügung.The C# 8.0 compiler is available starting with Visual Studio 2019, 16.3, or the .NET Core 3.0 SDK or later.

Einschränkungen von ErweiterungsmethodenLimitations of extension methods

Eine Möglichkeit, Verhalten zu implementieren, das als Teil einer Schnittstelle auftritt, besteht darin, Erweiterungsmethoden zu definieren, die das Standardverhalten bereitstellen.One way you can implement behavior that appears as part of an interface is to define extension methods that provide the default behavior. Schnittstellen deklarieren einen minimalen Satz von Membern und bieten gleichzeitig eine größere Oberfläche für jede Klasse, die diese Schnittstelle implementiert.Interfaces declare a minimum set of members while providing a greater surface area for any class that implements that interface. Die Erweiterungsmethoden in Enumerable stellen beispielsweise die Implementierung für jede beliebige Sequenz als Quelle einer LINQ-Abfrage bereit.For example, the extension methods in Enumerable provide the implementation for any sequence to be the source of a LINQ query.

Erweiterungsmethoden werden zur Kompilierzeit mithilfe des deklarierten Typs der Variablen aufgelöst.Extension methods are resolved at compile time, using the declared type of the variable. Klassen, die die Schnittstelle implementieren, können eine bessere Implementierung für jede beliebige Erweiterungsmethode bereitstellen.Classes that implement the interface can provide a better implementation for any extension method. Variablendeklarationen müssen dem implementierenden Typ entsprechen, damit der Compiler diese Implementierung auswählen kann.Variable declarations must match the implementing type to enable the compiler to choose that implementation. Wenn der Kompilierzeittyp mit der Schnittstelle übereinstimmt, werden Methodenaufrufe in die Erweiterungsmethode aufgelöst.When the compile-time type matches the interface, method calls resolve to the extension method. Ein weiteres Problem bei Erweiterungsmethoden ist, dass auf diese Methoden überall dort zugegriffen werden kann, wo auf die Klasse, die die Erweiterungsmethoden enthält, zugegriffen werden kann.Another concern with extension methods is that those methods are accessible wherever the class containing the extension methods is accessible. Klassen können nichts deklarieren, wenn sie in Erweiterungsmethoden deklarierte Funktionen bereitstellen oder nicht bereitstellen sollten.Classes cannot declare if they should or should not provide features declared in extension methods.

Ab C# 8.0 können Sie die Standardimplementierungen als Schnittstellenmethoden deklarieren.Starting with C# 8.0, you can declare the default implementations as interface methods. Anschließend verwendet jede Klasse automatisch die Standardimplementierung.Then, every class automatically uses the default implementation. Jede Klasse, die eine bessere Implementierung bereitstellen kann, kann die Definition der Schnittstellenmethode mit einem besseren Algorithmus überschreiben.Any class that can provide a better implementation can override the interface method definition with a better algorithm. In gewisser Weise klingt diese Technik ähnlich wie die Verwendung von Erweiterungsmethoden.In one sense, this technique sounds similar to how you could use extension methods.

In diesem Artikel erfahren Sie, wie Standardschnittstellenimplementierungen neue Szenarien ermöglichen.In this article, you'll learn how default interface implementations enable new scenarios.

Entwerfen der AnwendungDesign the application

Stellen Sie sich eine Anwendung für Smart Home-Automatisierung vor.Consider a home automation application. Sie haben wahrscheinlich viele verschiedene Arten von Leuchten und Indikatoren, die im gesamten Haus verwendet werden könnten.You probably have many different types of lights and indicators that could be used throughout the house. Jede Leuchte muss APIs unterstützen, um sie ein- und auszuschalten und den aktuellen Zustand zu melden.Every light must support APIs to turn them on and off, and to report the current state. Einige Leuchten und Indikatoren unterstützen möglicherweise andere Funktionen, beispielsweise:Some lights and indicators may support other features, such as:

  • Einschalten der Leuchte und Ausschalten anhand eines Timers.Turn light on, then turn it off after a timer.
  • Blinklichtfunktion der Leuchte für einen bestimmten Zeitraum.Blink the light for a period of time.

Einige dieser erweiterten Funktionen können auf Geräten emuliert werden, die den minimalen Satz unterstützen.Some of these extended capabilities could be emulated in devices that support the minimal set. Dies bedeutet, dass eine Standardimplementierung bereitgestellt wird.That indicates providing a default implementation. Für Geräte, die über mehr integrierte Funktionen verfügen, würde die Gerätesoftware die nativen Funktionen verwenden.For those devices that have more capabilities built in, the device software would use the native capabilities. Für andere Leuchten können sie sich entscheiden, die Schnittstelle zu implementieren und die Standardimplementierung zu verwenden.For other lights, they could choose to implement the interface and use the default implementation.

Standardschnittstellenmember sind eine bessere Lösung für dieses Szenario als Erweiterungsmethoden.Default interface members is a better solution for this scenario than extension methods. Klassenautoren können steuern, welche Schnittstellen sie implementieren möchten.Class authors can control which interfaces they choose to implement. Diese Schnittstellen, die sie auswählen, sind als Methoden verfügbar.Those interfaces they choose are available as methods. Da Standardschnittstellenmethoden standardmäßig virtuell sind, wählt die Methodenbindung außerdem immer die Implementierung in der-Klasse aus.In addition, because default interface methods are virtual by default, the method dispatch always chooses the implementation in the class.

Erstellen wir den Code, um diese Unterschiede zu veranschaulichen.Let's create the code to demonstrate these differences.

Erstellen von SchnittstellenCreate interfaces

Beginnen Sie, indem Sie die Schnittstelle erstellen, die das Verhalten für alle Leuchten definiert:Start by creating the interface that defines the behavior for all lights:

public interface ILight
{
    void SwitchOn();
    void SwitchOff();
    bool IsOn();
}

Eine einfache Deckenleuchte könnte diese Schnittstelle wie im folgenden Code dargestellt implementieren:A basic overhead light fixture might implement this interface as shown in the following code:

public class OverheadLight : ILight
{
    private bool isOn;
    public bool IsOn() => isOn;
    public void SwitchOff() => isOn = false;
    public void SwitchOn() => isOn = true;

    public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";

}

In diesem Tutorial werden keine IoT-Geräte durch den Code gesteuert, sondern diese Aktivitäten werden emuliert, indem Nachrichten in die Konsole geschrieben werden.In this tutorial, the code doesn't drive IoT devices, but emulates those activities by writing messages to the console. Sie können den Code untersuchen, ohne Ihr Haus zu automatisieren.You can explore the code without automating your house.

Definieren wir nun die Schnittstelle für eine Leuchte, die nach einem Timeout automatisch ausgeschaltet werden kann:Next, let's define the interface for a light that can automatically turn off after a timeout:

public interface ITimerLight : ILight
{
    Task TurnOnFor(int duration);
}

Sie könnten eine Basisimplementierung zur Deckenleuchte hinzufügen, aber eine bessere Lösung besteht darin, diese Schnittstellendefinition zu ändern, um eine virtual-Standardimplementierung bereitzustellen:You could add a basic implementation to the overhead light, but a better solution is to modify this interface definition to provide a virtual default implementation:

public interface ITimerLight : ILight
{
    public async Task TurnOnFor(int duration)
    {
        Console.WriteLine("Using the default interface method for the ITimerLight.TurnOnFor.");
        SwitchOn();
        await Task.Delay(duration);
        SwitchOff();
        Console.WriteLine("Completed ITimerLight.TurnOnFor sequence.");
    }
}

Durch Hinzufügen dieser Änderung kann die OverheadLight-Klasse die Timerfunktion implementieren, indem sie Unterstützung für die Schnittstelle deklariert:By adding that change, the OverheadLight class can implement the timer function by declaring support for the interface:

public class OverheadLight : ITimerLight { }

Ein anderer Leuchtentyp unterstützt möglicherweise ein anspruchsvolleres Protokoll.A different light type may support a more sophisticated protocol. Er kann seine eigene Implementierung für TurnOnFor bereitstellen, wie im folgenden Code gezeigt:It can provide its own implementation for TurnOnFor, as shown in the following code:

public class HalogenLight : ITimerLight
{
    private enum HalogenLightState
    {
        Off,
        On,
        TimerModeOn
    }

    private HalogenLightState state;
    public void SwitchOn() => state = HalogenLightState.On;
    public void SwitchOff() => state = HalogenLightState.Off;
    public bool IsOn() => state != HalogenLightState.Off;
    public async Task TurnOnFor(int duration)
    {
        Console.WriteLine("Halogen light starting timer function.");
        state = HalogenLightState.TimerModeOn;
        await Task.Delay(duration);
        state = HalogenLightState.Off;
        Console.WriteLine("Halogen light finished custom timer function");
    }

    public override string ToString() => $"The light is {state}";
}

Im Gegensatz zum Überschreiben von Methoden der virtuellen Klasse verwendet die Deklaration von TurnOnFor in der HalogenLight-Klasse nicht das Schlüsselwort override.Unlike overriding virtual class methods, the declaration of TurnOnFor in the HalogenLight class does not use the override keyword.

Mix-und-Match-FunktionenMix and match capabilities

Die Vorteile von Standardschnittstellenmethoden werden deutlicher, wenn Sie erweiterte Funktionen einführen.The advantages of default interface methods become clearer as you introduce more advanced capabilities. Durch die Verwendung von Schnittstellen können Sie Mix-und-Match-Funktionen verwenden.Using interfaces enables you to mix and match capabilities. Außerdem kann jeder Klassenautor zwischen der Standardimplementierung und einer benutzerdefinierten Implementierung wählen.It also enables each class author to choose between the default implementation and a custom implementation. Fügen wir eine Schnittstelle mit einer Standardimplementierung für eine blinkende Leuchte hinzu:Let's add an interface with a default implementation for a blinking light:

public interface IBlinkingLight : ILight
{
    public async Task Blink(int duration, int repeatCount)
    {
        Console.WriteLine("Using the default interface method for IBlinkingLight.Blink.");
        for (int count = 0; count < repeatCount; count++)
        {
            SwitchOn();
            await Task.Delay(duration);
            SwitchOff();
            await Task.Delay(duration);
        }
        Console.WriteLine("Done with the default interface method for IBlinkingLight.Blink.");
    }
}

Die Standardimplementierung ermöglicht jeder Leuchte das Blinken.The default implementation enables any light to blink. Die Deckenleuchte kann sowohl Timer- als auch Blinkfunktionen mit der Standardimplementierung hinzufügen:The overhead light can add both timer and blink capabilities using the default implementation:

public class OverheadLight : ILight, ITimerLight, IBlinkingLight
{
    private bool isOn;
    public bool IsOn() => isOn;
    public void SwitchOff() => isOn = false;
    public void SwitchOn() => isOn = true;

    public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";
}

Ein neuer Leuchtentyp (LEDLight) unterstützt die Timerfunktion und die Blinkfunktion direkt.A new light type, the LEDLight supports both the timer function and the blink function directly. Dieser Leuchtenstil implementiert sowohl die ITimerLight- als auch die IBlinkingLight-Schnittstelle und überschreibt die Blink-Methode:This light style implements both the ITimerLight and IBlinkingLight interfaces, and overrides the Blink method:

public class LEDLight : IBlinkingLight, ITimerLight, ILight
{
    private bool isOn;
    public void SwitchOn() => isOn = true;
    public void SwitchOff() => isOn = false;
    public bool IsOn() => isOn;
    public async Task Blink(int duration, int repeatCount)
    {
        Console.WriteLine("LED Light starting the Blink function.");
        await Task.Delay(duration * repeatCount);
        Console.WriteLine("LED Light has finished the Blink funtion.");
    }

    public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";
}

Ein ExtraFancyLight-Element unterstützt ggf. Blink- und Timerfunktionen direkt:An ExtraFancyLight might support both blink and timer functions directly:

public class ExtraFancyLight : IBlinkingLight, ITimerLight, ILight
{
    private bool isOn;
    public void SwitchOn() => isOn = true;
    public void SwitchOff() => isOn = false;
    public bool IsOn() => isOn;
    public async Task Blink(int duration, int repeatCount)
    {
        Console.WriteLine("Extra Fancy Light starting the Blink function.");
        await Task.Delay(duration * repeatCount);
        Console.WriteLine("Extra Fancy Light has finished the Blink funtion.");
    }
    public async Task TurnOnFor(int duration)
    {
        Console.WriteLine("Extra Fancy light starting timer function.");
        await Task.Delay(duration);
        Console.WriteLine("Extra Fancy light finished custom timer function");
    }

    public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";
}

Das HalogenLight-Element, das Sie zuvor erstellt haben, unterstützt kein Blinken.The HalogenLight you created earlier doesn't support blinking. Fügen Sie IBlinkingLight daher nicht der Liste der unterstützten Schnittstellen dieses Elements hinzu.So, don't add the IBlinkingLight to the list of its supported interfaces.

Erkennen der Leuchtentypen mithilfe von MusterabgleichDetect the light types using pattern matching

Schreiben wir nun etwas Testcode.Next, let's write some test code. Sie können das Feature Musterabgleich von C# verwenden, um die Funktionen einer Leuchte zu ermitteln, indem Sie untersuchen, welche Schnittstellen sie unterstützt.You can make use of C#'s pattern matching feature to determine a light's capabilities by examining which interfaces it supports. Die folgende Methode gibt die unterstützten Fähigkeiten der einzelnen Leuchten aus:The following method exercises the supported capabilities of each light:

private static async Task TestLightCapabilities(ILight light)
{
    // Perform basic tests:
    light.SwitchOn();
    Console.WriteLine($"\tAfter switching on, the light is {(light.IsOn() ? "on" : "off")}");
    light.SwitchOff();
    Console.WriteLine($"\tAfter switching off, the light is {(light.IsOn() ? "on" : "off")}");

    if (light is ITimerLight timer)
    {
        Console.WriteLine("\tTesting timer function");
        await timer.TurnOnFor(1000);
        Console.WriteLine("\tTimer function completed");
    } 
    else
    {
        Console.WriteLine("\tTimer function not supported.");
    }

    if (light is IBlinkingLight blinker)
    {
        Console.WriteLine("\tTesting blinking function");
        await blinker.Blink(500, 5);
        Console.WriteLine("\tBlink function completed");
    }
    else
    {
        Console.WriteLine("\tBlink function not supported.");
    }
}

Der folgende Code in der Main-Methode erstellt alle Leuchtentypen nacheinander und testet die einzelnen Leuchten:The following code in your Main method creates each light type in sequence and tests that light:

static async Task Main(string[] args)
{
    Console.WriteLine("Testing the overhead light");
    var overhead = new OverheadLight();
    await TestLightCapabilities(overhead);
    Console.WriteLine();

    Console.WriteLine("Testing the halogen light");
    var halogen = new HalogenLight();
    await TestLightCapabilities(halogen);
    Console.WriteLine();

    Console.WriteLine("Testing the LED light");
    var led = new LEDLight();
    await TestLightCapabilities(led);
    Console.WriteLine();

    Console.WriteLine("Testing the fancy light");
    var fancy = new ExtraFancyLight();
    await TestLightCapabilities(fancy);
    Console.WriteLine();
}

So ermittelt der Compiler die beste ImplementierungHow the compiler determines best implementation

Dieses Szenario zeigt eine Basisschnittstelle ohne Implementierungen.This scenario shows a base interface without any implementations. Durch das Hinzufügen einer Methode zur ILight-Schnittstelle werden neue Komplexitäten eingeführt.Adding a method into the ILight interface introduces new complexities. Die Sprachregeln, die für Standardschnittstellenmethoden gelten, minimieren die Auswirkungen auf die konkreten Klassen, die mehrere abgeleitete Schnittstellen implementieren.The language rules governing default interface methods minimize the effect on the concrete classes that implement multiple derived interfaces. Erweitern wir die ursprüngliche Schnittstelle durch eine neue Methode, um zu zeigen, wie sich dadurch ihre Verwendung ändert.Let's enhance the original interface with a new method to show how that changes its use. Jede Indikatorleuchte kann ihren Energiestatus als Enumerationswert melden:Every indicator light can report its power status as an enumerated value:

public enum PowerStatus
{
    NoPower,
    ACPower,
    FullBattery,
    MidBattery,
    LowBattery
}

Die Standardimplementierung geht von einer Wechselstromversorgung aus:The default implementation assumes AC power:

public interface ILight
{
    void SwitchOn();
    void SwitchOff();
    bool IsOn();
    public PowerStatus Power() => PowerStatus.NoPower;
}

Diese Änderungen werden ordnungsgemäß kompiliert, auch wenn ExtraFancyLight Unterstützung für die ILight-Schnittstelle und die beiden abgeleiteten Schnittstellen ITimerLight und IBlinkingLight deklariert.These changes compile cleanly, even though the ExtraFancyLight declares support for the ILight interface and both derived interfaces, ITimerLight and IBlinkingLight. Es gibt nur eine „nächste“ Implementierung, die in der ILight-Schnittstelle deklariert ist.There's only one "closest" implementation declared in the ILight interface. Jede Klasse, die eine Überschreibung deklariert hat, würde zur „nächsten“ Implementierung werden.Any class that declared an override would become the one "closest" implementation. Sie haben in den vorhergehenden Klassen Beispiele gesehen, die die Member anderer abgeleiteter Schnittstellen überschreiben.You saw examples in the preceding classes that overrode the members of other derived interfaces.

Vermeiden Sie das Überschreiben derselben Methode in mehreren abgeleiteten Schnittstellen.Avoid overriding the same method in multiple derived interfaces. Auf diese Weise wird ein mehrdeutiger Methodenaufruf erstellt, wenn eine Klasse beide abgeleiteten Schnittstellen implementiert.Doing so creates an ambiguous method call whenever a class implements both derived interfaces. Der Compiler kann keine einzelne bessere Methode auswählen, sodass er einen Fehler ausgibt.The compiler can't pick a single better method so it issues an error. Wenn z.B. sowohl IBlinkingLight als auch ITimerLight eine Überschreibung von PowerStatus implementiert hat, müsste OverheadLight eine spezifischere Überschreibung bereitstellen.For example, if both the IBlinkingLight and ITimerLight implemented an override of PowerStatus, the OverheadLight would need to provide a more specific override. Andernfalls kann der Compiler nicht zwischen den Implementierungen in den beiden abgeleiteten Schnittstellen wählen.Otherwise, the compiler can't pick between the implementations in the two derived interfaces. Sie können diese Situation in der Regel vermeiden, indem Sie Schnittstellendefinitionen klein halten und sich auf eine Funktion konzentrieren.You can usually avoid this situation by keeping interface definitions small and focused on one feature. In diesem Szenario ist jede Funktion einer Leuchte eine eigene Schnittstelle. Mehrere Schnittstellen werden nur von Klassen geerbt.In this scenario, each capability of a light is its own interface; multiple interfaces are only inherited by classes.

Dieses Beispiel zeigt ein Szenario, in dem Sie diskrete Features definieren können, die in Klassen gemischt werden können.This sample shows one scenario where you can define discrete features that can be mixed into classes. Sie deklarieren einen beliebigen Satz unterstützter Funktionen, indem Sie deklarieren, welche Schnittstellen eine Klasse unterstützt.You declare any set of supported functionality by declaring which interfaces a class supports. Durch die Verwendung von virtuellen Standardschnittstellenmethoden können Klassen eine andere Implementierung für beliebige oder alle Schnittstellenmethoden verwenden oder definieren.The use of virtual default interface methods enables classes to use or define a different implementation for any or all the interface methods. Diese Sprachfunktion bietet neue Möglichkeiten zum Modellieren der realen Systeme, die Sie entwickeln.This language capability provides new ways to model the real-world systems you're building. Standardschnittstellenmethoden bieten eine bessere Möglichkeit, verwandte Klassen auszudrücken, die Mix-and-Match-Funktionen verwenden, indem sie virtuelle Implementierungen dieser Funktionen verwenden.Default interface methods provide a clearer way to express related classes that may mix and match different features using virtual implementations of those capabilities.