Didacticiel : mettre à jour les interfaces avec les C# méthodes d’interface par défaut dans 8,0Tutorial: Update interfaces with default interface methods in C# 8.0

Depuis C# 8.0 sur .NET Core 3.0, vous pouvez définir une implémentation lorsque vous déclarez un membre d’une interface.Beginning with C# 8.0 on .NET Core 3.0, you can define an implementation when you declare a member of an interface. Le scénario le plus courant consiste à ajouter de manière sécurisée des membres à une interface déjà publiée et utilisée par de nombreux clients.The most common scenario is to safely add members to an interface already released and used by innumerable clients.

Dans ce tutoriel, vous allez apprendre à :In this tutorial, you'll learn how to:

  • Étendre des interfaces de manière sécurisée en ajoutant des méthodes avec des implémentationsExtend interfaces safely by adding methods with implementations.
  • Créer des implémentations paramétrables pour fournir une plus grande flexibilitéCreate parameterized implementations to provide greater flexibility.
  • Permettre aux implémenteurs de fournir une implémentation plus spécifique sous la forme d’un remplacementEnable implementers to provide a more specific implementation in the form of an override.

Configuration requisePrerequisites

Vous devez configurer votre ordinateur pour exécuter .NET Core, y compris le C# compilateur 8,0.You’ll need to set up your machine to run .NET Core, including the C# 8.0 compiler. Le C# compilateur 8,0 est disponible à partir de Visual Studio 2019 version 16,3 ou du Kit de développement logiciel (SDK) .net Core 3,0.The C# 8.0 compiler is available starting with Visual Studio 2019 version 16.3 or the .NET Core 3.0 SDK.

Vue d’ensemble du scénarioScenario overview

Ce tutoriel commence par la version 1 d’une bibliothèque de relation client.This tutorial starts with version 1 of a customer relationship library. Vous pouvez obtenir l’application de démarrage à partir de notre dépôt samples sur GitHub.You can get the starter application on our samples repo on GitHub. L’entreprise qui a créé cette bibliothèque espérait la voir adoptée par les clients ayant des applications existantes.The company that built this library intended customers with existing applications to adopt their library. Elle a fourni des définitions d’interface minimales destinées à être implémentées par les utilisateurs de sa bibliothèque.They provided minimal interface definitions for users of their library to implement. Voici la définition d’interface pour un client :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; }
}

Elle a défini une deuxième interface qui représente une commande :They defined a second interface that represents an order:

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

À partir de ces interfaces, l’équipe a pu générer une bibliothèque pour les utilisateurs permettant de créer une meilleure expérience pour ses clients.From those interfaces, the team could build a library for their users to create a better experience for their customers. Son objectif était de créer une relation plus étroite avec les clients existants et d’améliorer ses relations avec les nouveaux clients.Their goal was to create a deeper relationship with existing customers and improve their relationships with new customers.

À présent, il est temps de mettre à niveau la bibliothèque pour la prochaine version.Now, it's time to upgrade the library for the next release. L’une des fonctionnalités demandées permet une remise de fidélité pour les clients qui ont un grand nombre de commandes.One of the requested features enables a loyalty discount for customers that have lots of orders. Cette nouvelle remise de fidélité est appliquée chaque fois qu’un client passe une commande.This new loyalty discount gets applied whenever a customer makes an order. La remise spécifique est une propriété de chaque client.The specific discount is a property of each individual customer. Chaque implémentation de ICustomer peut définir des règles différentes pour la remise de fidélité.Each implementation of ICustomer can set different rules for the loyalty discount.

La façon la plus naturelle d’ajouter cette fonctionnalité consiste à améliorer l’interface ICustomer avec une méthode pour appliquer une remise de fidélité.The most natural way to add this functionality is to enhance the ICustomer interface with a method to apply any loyalty discount. Cette suggestion de conception a posé problème aux développeurs expérimentés : «les interfaces sont immuables une fois qu’elles ont été publiées !This design suggestion caused concern among experienced developers: "Interfaces are immutable once they've been released! Il s’agit d’une modification avec rupture ! »This is a breaking change!" C# 8.0 ajoute des implémentations d’interface par défaut pour la mise à niveau des interfaces.C# 8.0 adds default interface implementations for upgrading interfaces. Les auteurs de bibliothèque peuvent ajouter de nouveaux membres à l’interface et fournir une implémentation par défaut pour ces membres.The library authors can add new members to the interface and provide a default implementation for those members.

Les implémentations d’interface par défaut permettent aux développeurs de mettre à niveau une interface tout en laissant la possibilité aux implémenteurs de substituer cette implémentation.Default interface implementations enable developers to upgrade an interface while still enabling any implementors to override that implementation. Les utilisateurs de la bibliothèque peuvent accepter l’implémentation par défaut en tant que modification sans rupture.Users of the library can accept the default implementation as a non-breaking change. Si leurs règles métier sont différentes, ils peuvent opérer un remplacement.If their business rules are different, they can override.

Mettre à niveau avec les méthodes d’interface par défautUpgrade with default interface methods

L’équipe s’est mise d’accord sur l’implémentation par défaut la plus vraisemblable : une remise de fidélité pour les clients.The team agreed on the most likely default implementation: a loyalty discount for customers.

La mise à niveau doit fournir la fonctionnalité permettant de définir deux propriétés : le nombre de commandes nécessaires pour pouvoir bénéficier de la remise et le pourcentage de celle-ci.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. Cela en fait un scénario parfait pour les méthodes d’interface par défaut.This makes it a perfect scenario for default interface methods. Vous pouvez ajouter une méthode à l’interface ICustomer et fournir l’implémentation la plus probable.You can add a method to the ICustomer interface, and provide the most likely implementation. Toutes les implémentations existantes et nouvelles peuvent utiliser l’implémentation par défaut ou fournir la leur.All existing, and any new implementations can use the default implementation, or provide their own.

Tout d’abord, ajoutez la nouvelle méthode à l’implémentation :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;
}

L’auteur de la bibliothèque a écrit un premier test pour vérifier l’implémentation :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()}");

Notez la partie suivante du test :Notice the following portion of the test:

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

Ce cast de SampleCustomer en ICustomer est nécessaire.That cast from SampleCustomer to ICustomer is necessary. La classe SampleCustomer n’a pas besoin de fournir une implémentation pour ComputeLoyaltyDiscount ; elle est fournie par l’interface ICustomer.The SampleCustomer class doesn't need to provide an implementation for ComputeLoyaltyDiscount; that's provided by the ICustomer interface. Toutefois, la classe SampleCustomer n’hérite pas les membres de ses interfaces.However, the SampleCustomer class doesn't inherit members from its interfaces. Cette règle n’a pas changé.That rule hasn't changed. Pour qu’il soit possible d’appeler n’importe quelle méthode déclarée et implémentée dans l’interface, la variable doit être du même type que l’interface, ICustomer dans cet exemple.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.

Fournir le paramétrageProvide parameterization

C’est un bon début.That's a good start. Toutefois, l’implémentation par défaut est trop restrictive.But, the default implementation is too restrictive. De nombreux consommateurs de ce système peuvent choisir des seuils différents pour le nombre d’achats, une durée différente pour la qualité de membre ou un pourcentage de remise différent.Many consumers of this system may choose different thresholds for number of purchases, a different length of membership, or a different percentage discount. Vous pouvez procurer une meilleure expérience de mise à niveau à davantage de clients en fournissant un moyen de définir ces paramètres.You can provide a better upgrade experience for more customers by providing a way to set those parameters. Nous allons ajouter une méthode statique qui définit ces trois paramètres de contrôle de l’implémentation par défaut :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;
}

De nombreuses nouvelles fonctionnalités de langage sont présentées dans ce petit fragment de code.There are many new language capabilities shown in that small code fragment. Les interfaces peuvent maintenant inclure des membres statiques, notamment des champs et des méthodes.Interfaces can now include static members, including fields and methods. Différents modificateurs d’accès sont également activés.Different access modifiers are also enabled. Les champs supplémentaires sont privés, tandis que la nouvelle méthode est publique.The additional fields are private, the new method is public. Tout modificateur est autorisé sur les membres d’interface.Any of the modifiers are allowed on interface members.

Les applications qui utilisent la formule générale pour le calcul de la remise de fidélité, mais des paramètres différents, n’ont pas besoin de fournir une implémentation personnalisée ; elles peuvent définir les arguments par le biais d’une méthode statique.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. Par exemple, le code suivant définit une « évaluation du client » qui récompense tout client membre depuis plus d’un mois :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()}");

Étendre l’implémentation par défautExtend the default implementation

Le code que vous avez ajouté jusqu’à présent a fourni une implémentation pratique pour les scénarios où les utilisateurs veulent quelque chose comme l’implémentation par défaut ou pour fournir un ensemble de règles non lié.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. En guise de dernière fonctionnalité, nous allons refactoriser le code un peu pour permettre les scénarios où les utilisateurs sont susceptibles de vouloir générer l’implémentation par défaut.For a final feature, let's refactor the code a bit to enable scenarios where users may want to build on the default implementation.

Imaginez une start-up qui souhaite attirer de nouveaux clients.Consider a startup that wants to attract new customers. Elle offre une remise de 50 % sur la première commande d’un nouveau client.They offer a 50% discount off a new customer's first order. Pour leur part, les clients existants bénéficient de la remise standard.Otherwise, existing customers get the standard discount. L’auteur de la bibliothèque doit déplacer l’implémentation par défaut vers une méthode protected static afin que toute classe qui implémente cette interface puisse réutiliser le code dans son implémentation.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. L’implémentation par défaut du membre d’interface appelle également cette méthode partagée :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;
}

Dans une implémentation d’une classe qui implémente cette interface, le remplacement peut appeler la méthode d’assistance statique et étendre cette logique pour fournir la remise « nouveau client » :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);
}

Vous pouvez voir l’intégralité du code terminé dans notre dépôt d’exemples sur GitHub.You can see the entire finished code in our samples repo on GitHub. Vous pouvez obtenir l’application de démarrage à partir de notre dépôt samples sur GitHub.You can get the starter application on our samples repo on GitHub.

Ces nouvelles fonctionnalités signifient que les interfaces peuvent être mises à jour de manière sécurisée quand il existe une implémentation par défaut raisonnable pour les nouveaux membres.These new features mean that interfaces can be updated safely when there's a reasonable default implementation for those new members. Concevez soigneusement les interfaces pour exprimer des idées fonctionnelles uniques pouvant être implémentées par plusieurs classes.Carefully design interfaces to express single functional ideas that can be implemented by multiple classes. Cela facilite la mise à niveau de ces définitions d’interface quand de nouvelles exigences sont découvertes pour cette même idée fonctionnelle.That makes it easier to upgrade those interface definitions when new requirements are discovered for that same functional idea.