Tutorial: Aktualisieren von Schnittstellen mit Standardschnittstellenmethoden in C# 8.0Tutorial: Update interfaces with default interface methods in C# 8.0

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. Das häufigste Szenario ist das sichere Hinzufügen von Membern zu einer Schnittstelle, die bereits veröffentlicht ist und von unzähligen Clients verwendet wird.The most common scenario is to safely add members to an interface already released and used by innumerable clients.

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

  • Erweitern Sie Schnittstellen problemlos durch Hinzufügen von Methoden mit Implementierungen.Extend interfaces safely by adding methods with implementations.
  • Erstellen Sie parametrisierte Implementierungen, um größere Flexibilität zu bieten.Create parameterized implementations to provide greater flexibility.
  • Ermöglichen Sie Implementierern, eine spezifischere Implementierung in Form einer Überschreibung zu bieten.Enable implementers to provide a more specific implementation in the form of an override.

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 Version 16.3 oder mit dem .NET Core 3.0 SDK zur Verfügung.The C# 8.0 compiler is available starting with Visual Studio 2019 version 16.3 or the .NET Core 3.0 SDK.

Übersicht über das SzenarioScenario overview

Dieses Tutorial beginnt mit Version 1 einer Kundenbeziehungsbibliothek.This tutorial starts with version 1 of a customer relationship library. Sie erhalten die Startanwendung von unserem Beispielerepository auf GitHub.You can get the starter application on our samples repo on GitHub. Das Unternehmen, das diese Bibliothek erstellt hat, beabsichtigte, dass Kunden mit vorhandenen Anwendungen seine Bibliothek verwenden.The company that built this library intended customers with existing applications to adopt their library. Minimale Schnittstellendefinitionen wurden bereitgestellt, die Benutzer ihrer Bibliothek implementieren sollten.They provided minimal interface definitions for users of their library to implement. So sieht die Schnittstellendefinition für einen Kunden aus:Here's the interface definition for a customer:

public interface ICustomer
{
    IEnumerable<IOrder> PreviousOrders { get; }

    DateTime DateJoined { get; }
    DateTime? LastOrder { get; }
    string Name { get; }
    IDictionary<DateTime, string> Reminders { get; }
}

Eine zweite Schnittstelle wurde definiert, die eine Bestellung darstellt:They defined a second interface that represents an order:

public interface IOrder
{
    DateTime Purchased { get; }
    decimal Cost { get; }
}

Von diesen Schnittstellen aus konnte das Team eine Bibliothek für die Benutzer erstellen, um den Kunden eine bessere Benutzererfahrung zu bieten.From those interfaces, the team could build a library for their users to create a better experience for their customers. Das Ziel bestand darin, eine intensivere Beziehung zu Bestandskunden aufzubauen und ihre Beziehungen zu neuen Kunden zu verbessern.Their goal was to create a deeper relationship with existing customers and improve their relationships with new customers.

Jetzt ist es Zeit, die Bibliothek für das nächste Release zu aktualisieren.Now, it's time to upgrade the library for the next release. Eines der angeforderten Features gewährt Kunden, die viele Bestellungen aufgeben, einen Treuerabatt.One of the requested features enables a loyalty discount for customers that have lots of orders. Dieser neue Treuerabatt wird angewendet, wenn ein Kunde eine Bestellung aufgibt.This new loyalty discount gets applied whenever a customer makes an order. Der spezifische Rabatt ist eine Eigenschaft jedes einzelnen Kunden.The specific discount is a property of each individual customer. Jede Implementierung von ICustomer kann andere Regeln für den Treuerabatt festlegen.Each implementation of ICustomer can set different rules for the loyalty discount.

Die naheliegendste Methode zum Hinzufügen dieser Funktionalität ist die Verbesserung der ICustomer-Schnittstelle mit einer Methode zur Anwendung eines Treuerabatts.The most natural way to add this functionality is to enhance the ICustomer interface with a method to apply any loyalty discount. Diese Entwurfsempfehlung löste bei erfahrenen Entwicklern Bedenken aus: „Schnittstellen sind unveränderlich, sobald sie veröffentlicht sind!This design suggestion caused concern among experienced developers: "Interfaces are immutable once they've been released! Dies ist eine einschneidende Änderung!“This is a breaking change!" C# 8.0 fügt Standardschnittstellenimplementierungen zum Aktualisieren von Schnittstellen hinzu.C# 8.0 adds default interface implementations for upgrading interfaces. Die Autoren der Bibliothek können der Schnittstelle neue Member hinzufügen und eine Standardimplementierung für diese Member bereitstellen.The library authors can add new members to the interface and provide a default implementation for those members.

Mit Implementierungen von Standardschnittstellen können Entwickler eine Schnittstelle aktualisieren, während gleichzeitig alle Implementierer diese Implementierung überschreiben können.Default interface implementations enable developers to upgrade an interface while still enabling any implementors to override that implementation. Benutzer der Bibliothek können die standardmäßige Implementierung als eine nicht unterbrechende Änderung akzeptieren.Users of the library can accept the default implementation as a non-breaking change. Wenn ihre Geschäftsregeln anders sind, können sie überschreiben.If their business rules are different, they can override.

Upgraden mit StandardschnittstellenmethodenUpgrade with default interface methods

Das Team stimmte der wahrscheinlichsten Standardimplementierung zu: einem Treuerabatt für Kunden.The team agreed on the most likely default implementation: a loyalty discount for customers.

Das Upgrade sollte die Funktionalität zum Festlegen von zwei Eigenschaften bieten: die für den Rabatt erforderliche Anzahl an Bestellungen sowie den Prozentsatz des Rabatts.The upgrade should provide the functionality to set two properties: the number of orders needed to be eligible for the discount, and the percentage of the discount. Damit wird es zum idealen Szenario für Standardschnittstellenmethoden.This makes it a perfect scenario for default interface methods. Sie können der ICustomer-Schnittstelle eine Methode hinzufügen und die wahrscheinlichste Implementierung bereitstellen.You can add a method to the ICustomer interface, and provide the most likely implementation. Alle vorhandenen und alle neuen Implementierungen können die Standardimplementierung verwenden oder ihre eigene angeben.All existing, and any new implementations can use the default implementation, or provide their own.

Fügen Sie zunächst der Implementierung die neue Methode hinzu:First, add the new method to the implementation:

// Version 1:
public decimal ComputeLoyaltyDiscount()
{
    DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
    if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
    {
        return 0.10m;
    }
    return 0;
}

Der Bibliotheksautor schrieb einen ersten Test zum Überprüfen der Implementierung:The library author wrote a first test to check the implementation:

SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))
{
    Reminders =
    {
        { new DateTime(2010, 08, 12), "childs's birthday" },
        { new DateTime(1012, 11, 15), "anniversary" }
    }
};


SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);
c.AddOrder(o);

o = new SampleOrder(new DateTime(2103, 7, 4), 25m);
c.AddOrder(o);

// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Beachten Sie den folgenden Teil des Tests:Notice the following portion of the test:

// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Diese Umwandlung von SampleCustomer zu ICustomer ist erforderlich.That cast from SampleCustomer to ICustomer is necessary. Die SampleCustomer-Klasse muss keine Implementierung für ComputeLoyaltyDiscount bereitstellen; dies erfolgt über die ICustomer-Schnittstelle.The SampleCustomer class doesn't need to provide an implementation for ComputeLoyaltyDiscount; that's provided by the ICustomer interface. Allerdings erbt die SampleCustomer-Klasse keine Member von ihren Schnittstellen.However, the SampleCustomer class doesn't inherit members from its interfaces. Diese Regel hat sich nicht geändert.That rule hasn't changed. Um jede in der Schnittstelle deklarierte und implementierte Methode aufrufen zu können, muss die Variable vom Typ der Schnittstelle sein, in diesem Beispiel ICustomer.In order to call any method declared and implemented in the interface, the variable must be the type of the interface, ICustomer in this example.

Bereitstellen der ParametrisierungProvide parameterization

Ein guter Anfang.That's a good start. Aber die Standardimplementierung ist zu restriktiv.But, the default implementation is too restrictive. Viele Nutzer dieses Systems könnten unterschiedliche Schwellenwerte für die Anzahl der Käufe, eine andere Dauer der Mitgliedschaft oder einen anderen Rabattprozentsatz auswählen.Many consumers of this system may choose different thresholds for number of purchases, a different length of membership, or a different percentage discount. Sie können mehr Kunden eine bessere Upgradeerfahrung bieten, indem Sie eine Möglichkeit zum Festlegen dieser Parameter bereitstellen.You can provide a better upgrade experience for more customers by providing a way to set those parameters. Wird fügen nun eine statische Methode hinzu, die diese drei, die Standardimplementierung steuernden Parameter festlegt:Let's add a static method that sets those three parameters controlling the default implementation:

// Version 2:
public static void SetLoyaltyThresholds(
    TimeSpan ago, 
    int minimumOrders = 10, 
    decimal percentageDiscount = 0.10m)
{
    length = ago;
    orderCount = minimumOrders;
    discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;

public decimal ComputeLoyaltyDiscount()
{
    DateTime start = DateTime.Now - length;

    if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))
    {
        return discountPercent;
    }
    return 0;
}

Dieses kleine Codefragment zeigt viele neue Sprachfunktionen.There are many new language capabilities shown in that small code fragment. Schnittstellen können nun statische Member einschließlich Feldern und Methoden enthalten.Interfaces can now include static members, including fields and methods. Verschiedene Zugriffsmodifizierer sind ebenfalls aktiviert.Different access modifiers are also enabled. Die zusätzlichen Felder sind privat, die neue Methode ist öffentlich.The additional fields are private, the new method is public. Beliebige der Modifizierer sind auf Schnittstellenmembern erlaubt.Any of the modifiers are allowed on interface members.

Anwendungen, die die allgemeine Formel zum Berechnen des Treuerabatts verwenden, aber andere Parameter, müssen keine benutzerdefinierte Implementierung bereitstellen; sie können die Argumente über eine statische Methode festlegen.Applications that use the general formula for computing the loyalty discount, but different parameters, don't need to provide a custom implementation; they can set the arguments through a static method. Der folgende Code legt z.B. eine „Kundenwertschätzung“ fest, die jeden Kunden mit mehr als einem Monat Mitgliedschaft belohnt:For example, the following code sets a "customer appreciation" that rewards any customer with more than one month's membership:

ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Erweitern der StandardimplementierungExtend the default implementation

Der Code, den Sie bisher hinzugefügt haben, hat eine einfache Implementierung für diese Szenarien ermöglicht, in denen Benutzer etwas wie die Standardimplementierung wünschen, oder um eine unzusammenhängende Gruppe von Regeln bereitzustellen.The code you've added so far has provided a convenient implementation for those scenarios where users want something like the default implementation, or to provide an unrelated set of rules. Für ein finales Feature werden wir den Code ein wenig umgestalten, um Szenarien zu ermöglichen, in denen Benutzer die Standardimplementierung erstellen möchten.For a final feature, let's refactor the code a bit to enable scenarios where users may want to build on the default implementation.

Stellen Sie sich ein Startupunternehmen vor, das neue Kunden gewinnen möchte.Consider a startup that wants to attract new customers. Es bietet einen Preisnachlass von 50% für die erste Bestellung eines neuen Kunden.They offer a 50% discount off a new customer's first order. Andernfalls erhalten Bestandskunden den Standardrabatt.Otherwise, existing customers get the standard discount. Der Bibliotheksautor muss die Standardimplementierung in eine protected static-Methode verschieben, sodass jede Klasse, die diese Schnittstelle implementiert, den Code in ihrer Implementierung wiederverwenden kann.The library author needs to move the default implementation into a protected static method so that any class implementing this interface can reuse the code in their implementation. Die Standardimplementierung des Schnittstellenmembers ruft diese freigegebene Methode ebenfalls auf:The default implementation of the interface member calls this shared method as well:

public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this);
protected static decimal DefaultLoyaltyDiscount(ICustomer c)
{
    DateTime start = DateTime.Now - length;

    if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount))
    {
        return discountPercent;
    }
    return 0;
}

In einer Implementierung einer Klasse, die diese Schnittstelle implementiert, kann die Überschreibung die statische Hilfsmethode aufrufen und diese Logik zum Bereitstellen des „Neuer Kunde“-Rabatts erweitern:In an implementation of a class that implements this interface, the override can call the static helper method, and extend that logic to provide the "new customer" discount:

public decimal ComputeLoyaltyDiscount()
{
   if (PreviousOrders.Any() == false)
        return 0.50m;
    else
        return ICustomer.DefaultLoyaltyDiscount(this);
}

Den vollständigen Code finden Sie in unserem Beispielrepository auf GitHub.You can see the entire finished code in our samples repo on GitHub. Sie erhalten die Startanwendung von unserem Beispielerepository auf GitHub.You can get the starter application on our samples repo on GitHub.

Diese neuen Features bedeuten, dass Schnittstellen problemlos aktualisiert werden können, wenn eine vernünftige Standardimplementierung für diese neuen Member vorhanden ist.These new features mean that interfaces can be updated safely when there's a reasonable default implementation for those new members. Entwerfen Sie Schnittstellen sorgfältig, um einzelne funktionale Konzepte auszudrücken, die von mehreren Klassen implementiert werden können.Carefully design interfaces to express single functional ideas that can be implemented by multiple classes. Dies erleichtert das Aktualisieren dieser Schnittstellendefinitionen, wenn neue Anforderungen für diese gleichen funktionalen Konzept entdeckt werden.That makes it easier to upgrade those interface definitions when new requirements are discovered for that same functional idea.