Didacticiel : combiner les fonctionnalités dans lors de la création de classes à l’aide d’interfaces avec les méthodes d’interface par défautTutorial: Mix functionality in when creating classes using interfaces with default interface methods

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. Cette fonctionnalité offre de nouvelles fonctionnalités qui vous permettent de définir des implémentations par défaut pour les fonctionnalités déclarées dans les interfaces.This feature provides new capabilities where you can define default implementations for features declared in interfaces. Les classes peuvent choisir quand remplacer les fonctionnalités, quand utiliser les fonctionnalités par défaut et quand ne pas déclarer la prise en charge des fonctionnalités discrètes.Classes can pick when to override functionality, when to use the default functionality, and when not to declare support for discrete features.

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

  • Créez des interfaces avec des implémentations qui décrivent des fonctionnalités discrètes.Create interfaces with implementations that describe discrete features.
  • Créez des classes qui utilisent les implémentations par défaut.Create classes that use the default implementations.
  • Créez des classes qui remplacent une partie ou la totalité des implémentations par défaut.Create classes that override some or all of the default implementations.

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, 16,3ou du Kit de développement logiciel (SDK) .net Core 3,0 ou version ultérieure.The C# 8.0 compiler is available starting with Visual Studio 2019, 16.3, or the .NET Core 3.0 SDK or later.

Limitations des méthodes d’extensionLimitations of extension methods

L’une des façons dont vous pouvez implémenter le comportement qui s’affiche dans le cadre d’une interface est de définir des méthodes d’extension qui fournissent le comportement par défaut.One way you can implement behavior that appears as part of an interface is to define extension methods that provide the default behavior. Les interfaces déclarent un ensemble minimal de membres tout en fournissant une plus grande surface d’exposition pour toute classe qui implémente cette interface.Interfaces declare a minimum set of members while providing a greater surface area for any class that implements that interface. Par exemple, les méthodes d’extension dans Enumerable fournissent l’implémentation pour toute séquence comme source d’une requête LINQ.For example, the extension methods in Enumerable provide the implementation for any sequence to be the source of a LINQ query.

Les méthodes d’extension sont résolues au moment de la compilation, à l’aide du type déclaré de la variable.Extension methods are resolved at compile time, using the declared type of the variable. Les classes qui implémentent l’interface peuvent fournir une meilleure implémentation pour toute méthode d’extension.Classes that implement the interface can provide a better implementation for any extension method. Les déclarations de variables doivent correspondre au type d’implémentation pour permettre au compilateur de choisir cette implémentation.Variable declarations must match the implementing type to enable the compiler to choose that implementation. Lorsque le type au moment de la compilation correspond à l’interface, les appels de méthode sont résolus à la méthode d’extension.When the compile-time type matches the interface, method calls resolve to the extension method. Une autre préoccupation avec les méthodes d’extension est que ces méthodes sont accessibles partout où la classe qui contient les méthodes d’extension est accessible.Another concern with extension methods is that those methods are accessible wherever the class containing the extension methods is accessible. Les classes ne peuvent pas déclarer si elles doivent ou ne doivent pas fournir des fonctionnalités déclarées dans les méthodes d’extension.Classes cannot declare if they should or should not provide features declared in extension methods.

À partir C# de 8,0, vous pouvez déclarer les implémentations par défaut en tant que méthodes d’interface.Starting with C# 8.0, you can declare the default implementations as interface methods. Ensuite, chaque classe utilise automatiquement l’implémentation par défaut.Then, every class automatically uses the default implementation. Toute classe pouvant fournir une meilleure implémentation peut remplacer la définition de méthode d’interface par un meilleur algorithme.Any class that can provide a better implementation can override the interface method definition with a better algorithm. Dans un sens, cette technique ressemble à la façon dont vous pouvez utiliser les méthodes d’extension.In one sense, this technique sounds similar to how you could use extension methods.

Dans cet article, vous allez découvrir comment les implémentations d’interface par défaut permettent de nouveaux scénarios.In this article, you'll learn how default interface implementations enable new scenarios.

Concevoir l’applicationDesign the application

Prenons l’exemple d’une application domotique.Consider a home automation application. Vous disposez probablement de nombreux types d’éclairages et d’indicateurs qui peuvent être utilisés dans l’ensemble de la maison.You probably have many different types of lights and indicators that could be used throughout the house. Chaque lumière doit prendre en charge les API pour les activer et les désactiver, et pour signaler l’état actuel.Every light must support APIs to turn them on and off, and to report the current state. Certains éclairages et indicateurs peuvent prendre en charge d’autres fonctionnalités, telles que :Some lights and indicators may support other features, such as:

  • Activez le voyant, puis désactivez-le après une minuterie.Turn light on, then turn it off after a timer.
  • Faire clignoter la lumière pendant une période donnée.Blink the light for a period of time.

Certaines de ces fonctionnalités étendues peuvent être émulées sur les appareils qui prennent en charge l’ensemble minimal.Some of these extended capabilities could be emulated in devices that support the minimal set. Qui indique la fourniture d’une implémentation par défaut.That indicates providing a default implementation. Pour les appareils qui ont plus de fonctionnalités intégrées, le logiciel de l’appareil utilise les fonctionnalités natives.For those devices that have more capabilities built in, the device software would use the native capabilities. Pour les autres lumières, elles peuvent choisir d’implémenter l’interface et d’utiliser l’implémentation par défaut.For other lights, they could choose to implement the interface and use the default implementation.

Les membres d’interface par défaut sont une meilleure solution pour ce scénario que les méthodes d’extension.Default interface members is a better solution for this scenario than extension methods. Les auteurs de classe peuvent contrôler les interfaces qu’ils choisissent d’implémenter.Class authors can control which interfaces they choose to implement. Les interfaces qu’ils choisissent sont disponibles en tant que méthodes.Those interfaces they choose are available as methods. En outre, étant donné que les méthodes d’interface par défaut sont virtuelles par défaut, la distribution de méthode choisit toujours l’implémentation dans la classe.In addition, because default interface methods are virtual by default, the method dispatch always chooses the implementation in the class.

Créons le code pour illustrer ces différences.Let's create the code to demonstrate these differences.

Créer des interfacesCreate interfaces

Commencez par créer l’interface qui définit le comportement de toutes les lumières :Start by creating the interface that defines the behavior for all lights:

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

Un dispositif d’éclairage de surcharge de base peut implémenter cette interface comme indiqué dans le code suivant :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\"}";

}

Dans ce didacticiel, le code ne pilote pas les appareils IoT, mais émule ces activités en écrivant des messages sur la console.In this tutorial, the code doesn't drive IoT devices, but emulates those activities by writing messages to the console. Vous pouvez explorer le code sans automatiser votre maison.You can explore the code without automating your house.

Ensuite, nous allons définir l’interface pour une lumière qui peut s’éteindre automatiquement après un délai d’attente :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);
}

Vous pouvez ajouter une implémentation de base à la lumière de la surcharge, mais une meilleure solution consiste à modifier cette définition d’interface pour fournir une implémentation virtual par défaut :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.");
    }
}

En ajoutant cette modification, la classe OverheadLight peut implémenter la fonction timer en déclarant la prise en charge de l’interface :By adding that change, the OverheadLight class can implement the timer function by declaring support for the interface:

public class OverheadLight : ITimerLight { }

Un type de lumière différent peut prendre en charge un protocole plus sophistiqué.A different light type may support a more sophisticated protocol. Il peut fournir sa propre implémentation pour TurnOnFor, comme illustré dans le code suivant :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}";
}

Contrairement aux méthodes de la classe virtuelle de substitution, la déclaration de TurnOnFor dans la classe HalogenLight n’utilise pas le mot clé override.Unlike overriding virtual class methods, the declaration of TurnOnFor in the HalogenLight class does not use the override keyword.

Fonctionnalités Mix et matchMix and match capabilities

Les avantages des méthodes d’interface par défaut deviennent plus clairs lorsque vous introduisez des fonctionnalités plus avancées.The advantages of default interface methods become clearer as you introduce more advanced capabilities. L’utilisation d’interfaces vous permet de mélanger et de faire correspondre des fonctionnalités.Using interfaces enables you to mix and match capabilities. Il permet également à chaque auteur de classe de choisir entre l’implémentation par défaut et une implémentation personnalisée.It also enables each class author to choose between the default implementation and a custom implementation. Nous allons ajouter une interface avec une implémentation par défaut pour une lumière clignotante :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.");
    }
}

L’implémentation par défaut permet à tout éclairage de clignoter.The default implementation enables any light to blink. La lumière de la surcharge peut ajouter des fonctionnalités de minuterie et de clignotement à l’aide de l’implémentation par défaut :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\"}";
}

Un nouveau type de lumière, le LEDLight prend en charge la fonction de minuteur et la fonction Blink directement.A new light type, the LEDLight supports both the timer function and the blink function directly. Ce style clair implémente à la fois les interfaces ITimerLight et IBlinkingLight, et remplace la méthode Blink :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\"}";
}

Une ExtraFancyLight peut prendre en charge les fonctions de clignotement et de minuteur directement :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\"}";
}

Le HalogenLight que vous avez créé précédemment ne prend pas en charge le clignotement.The HalogenLight you created earlier doesn't support blinking. Par conséquent, n’ajoutez pas le IBlinkingLight à la liste des interfaces prises en charge.So, don't add the IBlinkingLight to the list of its supported interfaces.

Détecter les types de lumière à l’aide de critères spéciauxDetect the light types using pattern matching

Nous allons ensuite écrire du code de test.Next, let's write some test code. Vous pouvez utiliser la fonctionnalité C#de mise en correspondance des modèles de pour déterminer les capacités d’un éclairage en examinant les interfaces qu’il prend en charge.You can make use of C#'s pattern matching feature to determine a light's capabilities by examining which interfaces it supports. La méthode suivante exerce les fonctionnalités prises en charge de chaque lumière :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.");
    }
}

Le code suivant dans votre méthode Main crée chaque type de lumière dans la séquence et teste ce qui est clair :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();
}

Comment le compilateur détermine la meilleure implémentationHow the compiler determines best implementation

Ce scénario montre une interface de base sans implémentation.This scenario shows a base interface without any implementations. L’ajout d’une méthode dans l’interface ILight introduit de nouvelles complexités.Adding a method into the ILight interface introduces new complexities. Les règles de langage régissant les méthodes d’interface par défaut réduisent l’effet sur les classes concrètes qui implémentent plusieurs interfaces dérivées.The language rules governing default interface methods minimize the effect on the concrete classes that implement multiple derived interfaces. Nous allons améliorer l’interface d’origine avec une nouvelle méthode pour montrer comment cela modifie son utilisation.Let's enhance the original interface with a new method to show how that changes its use. Chaque témoin lumineux peut signaler son état d’alimentation comme une valeur énumérée :Every indicator light can report its power status as an enumerated value:

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

L’implémentation par défaut suppose une alimentation secteur :The default implementation assumes AC power:

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

Ces modifications sont compilées correctement, même si le ExtraFancyLight déclare la prise en charge de l’interface ILight et des deux interfaces dérivées, ITimerLight et IBlinkingLight.These changes compile cleanly, even though the ExtraFancyLight declares support for the ILight interface and both derived interfaces, ITimerLight and IBlinkingLight. Il n’y a qu’une seule implémentation « la plus proche » déclarée dans l’interface ILight.There's only one "closest" implementation declared in the ILight interface. Toute classe qui a déclaré une substitution devient l’implémentation « la plus proche ».Any class that declared an override would become the one "closest" implementation. Vous avez vu des exemples dans les classes précédentes qui a remplacé les membres d’autres interfaces dérivées.You saw examples in the preceding classes that overrode the members of other derived interfaces.

Évitez de substituer la même méthode dans plusieurs interfaces dérivées.Avoid overriding the same method in multiple derived interfaces. Si vous procédez ainsi, un appel de méthode ambigu est créé chaque fois qu’une classe implémente les deux interfaces dérivées.Doing so creates an ambiguous method call whenever a class implements both derived interfaces. Le compilateur ne peut pas choisir une méthode unique pour qu’il génère une erreur.The compiler can't pick a single better method so it issues an error. Par exemple, si les IBlinkingLight et ITimerLight implémentent une substitution de PowerStatus, le OverheadLight doit fournir une substitution plus spécifique.For example, if both the IBlinkingLight and ITimerLight implemented an override of PowerStatus, the OverheadLight would need to provide a more specific override. Sinon, le compilateur ne peut pas choisir entre les implémentations dans les deux interfaces dérivées.Otherwise, the compiler can't pick between the implementations in the two derived interfaces. Vous pouvez généralement éviter cette situation en conservant les définitions d’interface de petite taille et en vous concentrant sur une seule fonctionnalité.You can usually avoid this situation by keeping interface definitions small and focused on one feature. Dans ce scénario, chaque fonctionnalité d’une lumière est sa propre interface ; plusieurs interfaces sont héritées uniquement par les classes.In this scenario, each capability of a light is its own interface; multiple interfaces are only inherited by classes.

Cet exemple illustre un scénario dans lequel vous pouvez définir des fonctionnalités discrètes qui peuvent être mélangées dans des classes.This sample shows one scenario where you can define discrete features that can be mixed into classes. Vous déclarez tout ensemble de fonctionnalités prises en charge en déclarant les interfaces prises en charge par une classe.You declare any set of supported functionality by declaring which interfaces a class supports. L’utilisation de méthodes d’interface par défaut virtuelles permet aux classes d’utiliser ou de définir une implémentation différente pour toute ou partie des méthodes d’interface.The use of virtual default interface methods enables classes to use or define a different implementation for any or all the interface methods. Cette fonctionnalité de langage offre de nouvelles façons de modéliser les systèmes réels que vous créez.This language capability provides new ways to model the real-world systems you're building. Les méthodes d’interface par défaut offrent un moyen plus clair d’exprimer des classes connexes qui peuvent combiner et faire correspondre des fonctionnalités différentes à l’aide d’implémentations virtuelles de ces fonctionnalités.Default interface methods provide a clearer way to express related classes that may mix and match different features using virtual implementations of those capabilities.