Didacticiel : utiliser des critères spéciaux pour générer des algorithmes pilotés par type et pilotés par les données.Tutorial: Use pattern matching to build type-driven and data-driven algorithms.

C# 7 a introduit des fonctionnalités de critères spéciaux de base.C# 7 introduced basic pattern matching features. Ces fonctionnalités sont étendues en C# 8 et C# 9 avec de nouveaux modèles et expressions.Those features are extended in C# 8 and C# 9 with new expressions and patterns. Il est possible d’écrire des fonctionnalités qui se comportent comme si des types provenant potentiellement d’autres bibliothèques avaient été étendus.You can write functionality that behaves as though you extended types that may be in other libraries. Une autre utilisation des modèles consiste à créer des fonctionnalités requises par une application qui ne sont pas essentielles pour le type étendu.Another use for patterns is to create functionality your application requires that isn't a fundamental feature of the type being extended.

Ce didacticiel vous montre comment effectuer les opérations suivantes :In this tutorial, you'll learn how to:

  • Reconnaître les situations dans lesquelles les critères spéciaux sont nécessaires.Recognize situations where pattern matching should be used.
  • Utiliser des expressions de critères spéciaux pour implémenter des comportements en fonction des types et des valeurs de propriété.Use pattern matching expressions to implement behavior based on types and property values.
  • Combiner des critères spéciaux avec d’autres techniques pour créer des algorithmes complets.Combine pattern matching with other techniques to create complete algorithms.

PrérequisPrerequisites

Vous devez configurer votre ordinateur pour exécuter .NET 5, qui comprend le compilateur C# 9.You'll need to set up your machine to run .NET 5, which includes the C# 9 compiler. Le compilateur C# 9 est disponible à partir de Visual Studio 2019 version 16,9 Preview 1 ou .net 5,0 SDK.The C# 9 compiler is available starting with Visual Studio 2019 version 16.9 preview 1 or .NET 5.0 SDK.

Ce tutoriel suppose de connaître C# et .NET, y compris Visual Studio ou l’interface CLI .NET Core.This tutorial assumes you're familiar with C# and .NET, including either Visual Studio or the .NET Core CLI.

Scénarios des critères spéciauxScenarios for pattern matching

Le développement moderne passe souvent par l’intégration de données provenant de plusieurs sources et par la présentation d’informations et d’insights tirés de ces données dans une seule et même application.Modern development often includes integrating data from multiple sources and presenting information and insights from that data in a single cohesive application. Ni vous ni votre équipe n’aurez accès aux types qui représentent les données entrantes ou ne pourrez les contrôler.You and your team won't have control or access for all the types that represent the incoming data.

Une conception classique orientée objet exigerait de créer dans l’application des types représentant chacun des types de données de ces multiples sources.The classic object-oriented design would call for creating types in your application that represent each data type from those multiple data sources. L’application s’appuierait alors sur ces nouveaux types pour élaborer des hiérarchies d’héritage, créer des méthodes virtuelles et implémenter des abstractions.Then, your application would work with those new types, build inheritance hierarchies, create virtual methods, and implement abstractions. Ces techniques fonctionnent et représentent parfois les meilleurs outils disponibles.Those techniques work, and sometimes they are the best tools. Il est possible dans d’autres cas d’écrire du code moins longOther times you can write less code. et plus clair, à l’aide de techniques qui séparent les données des opérations qui les manipulent.You can write more clear code using techniques that separate the data from the operations that manipulate that data.

Ce tutoriel vise à créer et à explorer une application qui prend en entrée des données provenant de plusieurs sources externes pour un seul scénario.In this tutorial, you'll create and explore an application that takes incoming data from several external sources for a single scenario. Nous verrons en quoi les critères spéciaux constituent un moyen efficace de consommer et de traiter ces données d’une autre manière que ce que prévoit le système d’origine.You'll see how pattern matching provides an efficient way to consume and process that data in ways that weren't part of the original system.

Prenons une grande zone métropolitaine qui utilise des péages et des tarifs heures creuses/heures de pointe pour réguler le trafic.Consider a major metropolitan area that is using tolls and peak time pricing to manage traffic. Nous allons écrire une application qui calcule le péage en fonction du type de véhicule.You write an application that calculates tolls for a vehicle based on its type. Lors d’améliorations ultérieures, nous intégrerons aux tarifs le nombre de passagers du véhiculeLater enhancements incorporate pricing based on the number of occupants in the vehicle. ainsi que l’heure et le jour de la semaine.Further enhancements add pricing based on the time and the day of the week.

À partir de cette brève description, vous avez peut-être rapidement esquissé une hiérarchie d’objets pour modéliser ce système.From that brief description, you may have quickly sketched out an object hierarchy to model this system. Cependant, les données proviennent de plusieurs sources, et notamment d’autres systèmes de gestion de l’immatriculation des véhicules.However, your data is coming from multiple sources like other vehicle registration management systems. Ils fournissent des classes différentes pour modéliser ces données, sans proposer de modèle objet unique.These systems provide different classes to model that data and you don't have a single object model you can use. Dans ce tutoriel, nous allons utiliser ces classes simplifiées pour modéliser les données sur les véhicules issues de ces systèmes externes, comme dans le code suivant :In this tutorial, you'll use these simplified classes to model for the vehicle data from these external systems, as shown in the following code:

namespace ConsumerVehicleRegistration
{
    public class Car
    {
        public int Passengers { get; set; }
    }
}

namespace CommercialRegistration
{
    public class DeliveryTruck
    {
        public int GrossWeightClass { get; set; }
    }
}

namespace LiveryRegistration
{
    public class Taxi
    {
        public int Fares { get; set; }
    }

    public class Bus
    {
        public int Capacity { get; set; }
        public int Riders { get; set; }
    }
}

Vous pouvez télécharger l’exemple de démarrage à partir du référentiel GitHub dotnet/samples.You can download the starter code from the dotnet/samples GitHub repository. Comme on peut le voir, les classes de véhicules proviennent de différents systèmes et utilisent différents espaces de noms.You can see that the vehicle classes are from different systems, and are in different namespaces. Aucune classe de base commune, autre que System.Object, n’est exploitable.No common base class, other than System.Object can be leveraged.

Conception avec les critères spéciauxPattern matching designs

Le scénario utilisé dans ce didacticiel met en évidence les types de problèmes que la correspondance de modèle est bien adaptée pour résoudre :The scenario used in this tutorial highlights the kinds of problems that pattern matching is well suited to solve:

  • Les objets dont vous avez besoin ne figurent pas dans une hiérarchie d’objets correspondant à vos objectifs.The objects you need to work with aren't in an object hierarchy that matches your goals. Vous utilisez peut-être des classes faisant partie de systèmes non liés les uns aux autres.You may be working with classes that are part of unrelated systems.
  • Les fonctionnalités ajoutées ne font pas partie de l’abstraction de base de ces classes.The functionality you're adding isn't part of the core abstraction for these classes. Le péage payé change selon les types de véhicules, mais il ne s’agit pas d’une fonction essentielle du véhicule.The toll paid by a vehicle changes for different types of vehicles, but the toll isn't a core function of the vehicle.

Lorsque la forme des données et les opérations effectuées sur celles-ci ne sont pas décrites ensemble, les fonctionnalités de critères spéciaux de C# facilitent la tâche.When the shape of the data and the operations on that data are not described together, the pattern matching features in C# make it easier to work with.

Implémenter les calculs de péage de baseImplement the basic toll calculations

Le calcul de péage le plus élémentaire s’appuie uniquement sur le type de véhicule :The most basic toll calculation relies only on the vehicle type:

  • Car : 2,00 $.A Car is $2.00.
  • Taxi : 3,50 $.A Taxi is $3.50.
  • Bus : 5,00 $.A Bus is $5.00.
  • DeliveryTruck : 10,00 $.A DeliveryTruck is $10.00

Créez une classe TollCalculator et implémentez les critères spéciaux sur le type de véhicule pour obtenir le montant du péage.Create a new TollCalculator class, and implement pattern matching on the vehicle type to get the toll amount. Le code suivant montre l’implémentation initiale de TollCalculator.The following code shows the initial implementation of the TollCalculator.

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace toll_calculator
{
    public class TollCalculator
    {
        public decimal CalculateToll(object vehicle) =>
            vehicle switch
        {
            Car c           => 2.00m,
            Taxi t          => 3.50m,
            Bus b           => 5.00m,
            DeliveryTruck t => 10.00m,
            { }             => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
            null            => throw new ArgumentNullException(nameof(vehicle))
        };
    }
}

Le code précédent utilise une expression switch (différente d’une instruction switch) qui teste le modèle de type.The preceding code uses a switch expression (not the same as a switch statement) that tests the type pattern. Une expression switch commence par la variable, vehicle dans le code précédent, suivie du mot clé switch.A switch expression begins with the variable, vehicle in the preceding code, followed by the switch keyword. Viennent ensuite toutes les branches switch entre accolades.Next comes all the switch arms inside curly braces. L’expression switch ajoute d’autres perfectionnements à la syntaxe qui entoure l’instruction switch.The switch expression makes other refinements to the syntax that surrounds the switch statement. Le mot clé case est omis, et le résultat de chaque branche est une expression.The case keyword is omitted, and the result of each arm is an expression. Les deux dernières montrent une nouvelle fonctionnalité du langage.The last two arms show a new language feature. Le cas { } représente n’importe quel objet non Null ne correspondant à aucune des branches antérieures.The { } case matches any non-null object that didn't match an earlier arm. Il intercepte tous les types incorrects passés à cette méthode.This arm catches any incorrect types passed to this method. Le cas { } doit suivre les cas de chaque type de véhicule.The { } case must follow the cases for each vehicle type. Si l’ordre était inversé, le cas { } serait prioritaire.If the order were reversed, the { } case would take precedence. Enfin, le modèle null détecte si un null est passé à cette méthode.Finally, the null pattern detects when a null is passed to this method. Le null modèle peut être en dernier, car les autres modèles de type correspondent uniquement à un objet non null du type correct.The null pattern can be last because the other type patterns match only a non-null object of the correct type.

Pour tester ce code, utilisez le code suivant dans Program.cs :You can test this code using the following code in Program.cs:

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace toll_calculator
{
    class Program
    {
        static void Main(string[] args)
        {
            var tollCalc = new TollCalculator();

            var car = new Car();
            var taxi = new Taxi();
            var bus = new Bus();
            var truck = new DeliveryTruck();

            Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}");
            Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}");
            Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}");
            Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(truck)}");

            try
            {
                tollCalc.CalculateToll("this will fail");
            }
            catch (ArgumentException e)
            {
                Console.WriteLine("Caught an argument exception when using the wrong type");
            }
            try
            {
                tollCalc.CalculateToll(null!);
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("Caught an argument exception when using null");
            }
        }
    }
}

Ce code est inclus dans le projet de démarrage, mais il est commenté. Supprimez les commentaires et vous pouvez tester ce que vous avez écrit.That code is included in the starter project, but is commented out. Remove the comments, and you can test what you've written.

On commence à voir comment les modèles peuvent nous aider à créer des algorithmes dans lesquels le code et les données sont séparés.You're starting to see how patterns can help you create algorithms where the code and the data are separate. L’ expression switch teste le type et produit différentes valeurs en fonction des résultats.The switch expression tests the type and produces different values based on the results. Ce n’est que le début.That's only the beginning.

Ajouter la tarification en fonction du taux d’occupationAdd occupancy pricing

L’autorité de péage souhaite inciter les véhicules à circuler à capacité maximale.The toll authority wants to encourage vehicles to travel at maximum capacity. Elle a décidé de fixer un tarif plus élevé pour les véhicules transportant peu de passagers et un prix inférieur pour les véhicules pleins, de façon à encourager ces derniers :They've decided to charge more when vehicles have fewer passengers, and encourage full vehicles by offering lower pricing:

  • Voitures et taxis sans passager : +0,50 $.Cars and taxis with no passengers pay an extra $0.50.
  • Voitures et taxis avec deux passagers : remise de 0,50 $.Cars and taxis with two passengers get a $0.50 discount.
  • Voitures et taxis avec trois passagers ou plus : -1,00 $.Cars and taxis with three or more passengers get a $1.00 discount.
  • Bus remplis à moins de 50 % : +2,00 $.Buses that are less than 50% full pay an extra $2.00.
  • Bus remplis à plus de 90 % : -1,00 $.Buses that are more than 90% full get a $1.00 discount.

Il est possible d’implémenter ces règles avec le modèle de propriété dans la même expression switch.These rules can be implemented using the property pattern in the same switch expression. Le modèle de propriété examine les propriétés de l’objet une fois que le type a été déterminé.The property pattern examines properties of the object once the type has been determined. L’unique cas Car est étendu à quatre cas différents :The single case for a Car expands to four different cases:

vehicle switch
{
    Car {Passengers: 0}        => 2.00m + 0.50m,
    Car {Passengers: 1}        => 2.0m,
    Car {Passengers: 2}        => 2.0m - 0.50m,
    Car c                      => 2.00m - 1.0m,

    // ...
};

Les trois premiers cas testent le type Car, puis vérifient la valeur de la propriété Passengers.The first three cases test the type as a Car, then check the value of the Passengers property. Si les deux correspondent, cette expression est évaluée et retournée.If both match, that expression is evaluated and returned.

Les cas des taxis sont étendus de la même manière :You would also expand the cases for taxis in a similar manner:

vehicle switch
{
    // ...

    Taxi {Fares: 0}  => 3.50m + 1.00m,
    Taxi {Fares: 1}  => 3.50m,
    Taxi {Fares: 2}  => 3.50m - 0.50m,
    Taxi t           => 3.50m - 1.00m,

    // ...
};

Dans l’exemple précédent, la clause when a été omise sur le cas final.In the preceding example, the when clause was omitted on the final case.

Maintenant, implémentons les règles de taux d’occupation en développant les cas des bus, comme dans l’exemple suivant :Next, implement the occupancy rules by expanding the cases for buses, as shown in the following example:

vehicle switch
{
    // ...

    Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
    Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
    Bus b => 5.00m,

    // ...
};

L’autorité de péage ne se soucie pas du nombre de passagers des camions de livraison,The toll authority isn't concerned with the number of passengers in the delivery trucks. et ajuste le montant en fonction du poids du camion, en procédant ainsi :Instead, they adjust the toll amount based on the weight class of the trucks as follows:

  • Camions de plus de 5 000 lb : +5,00 $.Trucks over 5000 lbs are charged an extra $5.00.
  • Camions de moins de 3000 lb : -2,00 $.Light trucks under 3000 lbs are given a $2.00 discount.

Cette règle est implémentée dans le code suivant :That rule is implemented with the following code:

vehicle switch
{
    // ...

    DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
    DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
    DeliveryTruck t => 10.00m,
};

Le code précédent montre la clause when d’une branche switch.The preceding code shows the when clause of a switch arm. Cette clause when sert à tester les conditions autres que l’égalité sur une propriété.You use the when clause to test conditions other than equality on a property. Une fois que vous avez terminé, vous disposez d’une méthode qui ressemble au code suivant :When you've finished, you'll have a method that looks much like the following code:

vehicle switch
{
    Car {Passengers: 0}        => 2.00m + 0.50m,
    Car {Passengers: 1}        => 2.0m,
    Car {Passengers: 2}        => 2.0m - 0.50m,
    Car c                      => 2.00m - 1.0m,

    Taxi {Fares: 0}  => 3.50m + 1.00m,
    Taxi {Fares: 1}  => 3.50m,
    Taxi {Fares: 2}  => 3.50m - 0.50m,
    Taxi t           => 3.50m - 1.00m,

    Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
    Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
    Bus b => 5.00m,

    DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
    DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
    DeliveryTruck t => 10.00m,

    { }     => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
    null    => throw new ArgumentNullException(nameof(vehicle))
};

La plupart de ces branches switch sont des exemples de modèles récursifs.Many of these switch arms are examples of recursive patterns. Par exemple, Car { Passengers: 1} indique un modèle de constante à l’intérieur d’un modèle de propriété.For example, Car { Passengers: 1} shows a constant pattern inside a property pattern.

Il est possible de rendre ce code moins répétitif avec des switch imbriqués.You can make this code less repetitive by using nested switches. Dans les exemples précédents, Car et Taxi ont chacun quatre branches différentes.The Car and Taxi both have four different arms in the preceding examples. On peut créer dans les deux cas un modèle de type qui vient alimenter un modèle de propriété.In both cases, you can create a type pattern that feeds into a property pattern. Cette technique est illustrée dans le code suivant :This technique is shown in the following code:

public decimal CalculateToll(object vehicle) =>
    vehicle switch
    {
        Car c => c.Passengers switch
        {
            0 => 2.00m + 0.5m,
            1 => 2.0m,
            2 => 2.0m - 0.5m,
            _ => 2.00m - 1.0m
        },

        Taxi t => t.Fares switch
        {
            0 => 3.50m + 1.00m,
            1 => 3.50m,
            2 => 3.50m - 0.50m,
            _ => 3.50m - 1.00m
        },

        Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
        Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
        Bus b => 5.00m,

        DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
        DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
        DeliveryTruck t => 10.00m,

        { }  => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
        null => throw new ArgumentNullException(nameof(vehicle))
    };

Dans l’exemple précédent, l’expression récursive évite de répéter les branches Car et Taxi contenant des branches enfant qui testent la valeur de propriété.In the preceding sample, using a recursive expression means you don't repeat the Car and Taxi arms containing child arms that test the property value. Cette technique n’est pas utilisée pour les branches Bus et DeliveryTruck, car elles correspondent à des plages de test pour la propriété, et non à des valeurs discrètes.This technique isn't used for the Bus and DeliveryTruck arms because those arms are testing ranges for the property, not discrete values.

Ajouter la tarification heures creuses/heures de pointeAdd peak pricing

En guise de fonctionnalité finale, l’autorité de péage souhaite ajouter une tarification soumise à une contrainte horaire.For the final feature, the toll authority wants to add time sensitive peak pricing. Pendant les heures d’affluence du matin et du soir, les péages sont doublés.During the morning and evening rush hours, the tolls are doubled. Cette règle ne s’applique au trafic que dans un sens : en allant vers la ville le matin et en en sortant le soir.That rule only affects traffic in one direction: inbound to the city in the morning, and outbound in the evening rush hour. Le reste du temps, pendant la journée de travail, les péages augmentent de 50 %.During other times during the workday, tolls increase by 50%. Tard le soir et tôt le matin, ils sont réduits de 25 %.Late night and early morning, tolls are reduced by 25%. Le taux normal s’applique le weekend, quelle que soit l’heure.During the weekend, it's the normal rate, regardless of the time. Vous pouvez utiliser une série si if les else instructions et permettent d’exprimer ce qui suit à l’aide du code suivant :You could use a series if if and else statements to express this using the following code:

public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
{
    if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
        (timeOfToll.DayOfWeek == DayOfWeek.Sunday))
    {
        return 1.0m;
    }
    else
    {
        int hour = timeOfToll.Hour;
        if (hour < 6)
        {
            return 0.75m;
        } else if (hour < 10)
        {
            if (inbound)
            {
                return 2.0m;
            }
            else
            {
                return 1.0m;
            }
        }
        else if (hour < 16)
        {
            return 1.5m;
        }
        else if (hour < 20)
        {
            if (inbound)
            {
                return 1.0m;
            }
            else
            {
                return 2.0m;
            }
        }
        else // Overnight
        {
            return 0.75m;
        }
    }
}

Le code précédent fonctionne correctement, mais n’est pas lisible.The preceding code does work correctly, but isn't readable. Vous devez chaîner tous les cas d’entrée et les instructions imbriquées if pour la raison du code.You have to chain through all the input cases and the nested if statements to reason about the code. Au lieu de cela, vous allez utiliser des critères spéciaux pour cette fonctionnalité, mais vous allez l’intégrer à d’autres techniques.Instead, you'll use pattern matching for this feature, but you'll integrate it with other techniques. Vous pourriez élaborer une seule expression de critères spéciaux qui tiendrait compte de toutes les combinaisons direction/jour de la semaine/heure.You could build a single pattern match expression that would account for all the combinations of direction, day of the week, and time. Elle serait cependant complexe,The result would be a complicated expression. difficile à lire, à comprendreIt would be hard to read and difficult to understand. et à vérifier.That makes it hard to ensure correctness. Combinons plutôt ces méthodes afin de créer un tuple de valeurs décrivant de manière concise tous ces états.Instead, combine those methods to build a tuple of values that concisely describes all those states. Ensuite, utilisons les critères spéciaux pour calculer un multiplicateur de péage.Then use pattern matching to calculate a multiplier for the toll. Le tuple comporte trois conditions discrètes :The tuple contains three discrete conditions:

  • le jour : semaine / weekend ;The day is either a weekday or a weekend.
  • la tranche horaire ;The band of time when the toll is collected.
  • la direction : vers l’intérieur ou l’extérieur de la ville.The direction is into the city or out of the city

Le tableau suivant montre les combinaisons de valeurs d’entrée et le multiplicateur tarifaire :The following table shows the combinations of input values and the peak pricing multiplier:

JourDay TempsTime SensDirection PremiumPremium
Jour de la semaineWeekday Heure de pointe du matinmorning rush Vers l’intérieur de la villeinbound x 2,00x 2.00
Jour de la semaineWeekday Heure de pointe du matinmorning rush Vers l’extérieur de la villeoutbound x 1,00x 1.00
Jour de la semaineWeekday Journéedaytime Vers l’intérieur de la villeinbound x 1,50x 1.50
Jour de la semaineWeekday Journéedaytime Vers l’extérieur de la villeoutbound x 1,50x 1.50
Jour de la semaineWeekday Heure de pointe du soirevening rush Vers l’intérieur de la villeinbound x 1,00x 1.00
Jour de la semaineWeekday Heure de pointe du soirevening rush Vers l’extérieur de la villeoutbound x 2,00x 2.00
Jour de la semaineWeekday Nuitovernight Vers l’intérieur de la villeinbound x 0,75x 0.75
Jour de la semaineWeekday Nuitovernight Vers l’extérieur de la villeoutbound x 0,75x 0.75
Week-endWeekend Heure de pointe du matinmorning rush Vers l’intérieur de la villeinbound x 1,00x 1.00
Week-endWeekend Heure de pointe du matinmorning rush Vers l’extérieur de la villeoutbound x 1,00x 1.00
Week-endWeekend Journéedaytime Vers l’intérieur de la villeinbound x 1,00x 1.00
Week-endWeekend Journéedaytime Vers l’extérieur de la villeoutbound x 1,00x 1.00
Week-endWeekend Heure de pointe du soirevening rush Vers l’intérieur de la villeinbound x 1,00x 1.00
Week-endWeekend Heure de pointe du soirevening rush Vers l’extérieur de la villeoutbound x 1,00x 1.00
Week-endWeekend Nuitovernight Vers l’intérieur de la villeinbound x 1,00x 1.00
Week-endWeekend Nuitovernight Vers l’extérieur de la villeoutbound x 1,00x 1.00

Les trois variables produisent 16 combinaisons différentes.There are 16 different combinations of the three variables. On peut simplifier l’expression switch finale en associant certaines conditions.By combining some of the conditions, you'll simplify the final switch expression.

Le système qui collecte les péages utilise une structure DateTime pour indiquer l’heure de collecte.The system that collects the tolls uses a DateTime structure for the time when the toll was collected. Élaborons les méthodes de membre qui créent des variables à partir du tableau précédent.Build member methods that create the variables from the preceding table. La fonction suivante utilise une expression switch de critères spéciaux pour exprimer si DateTime représente un jour de weekend ou de semaine :The following function uses a pattern matching switch expression to express whether a DateTime represents a weekend or a weekday:

private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Monday    => true,
        DayOfWeek.Tuesday   => true,
        DayOfWeek.Wednesday => true,
        DayOfWeek.Thursday  => true,
        DayOfWeek.Friday    => true,
        DayOfWeek.Saturday  => false,
        DayOfWeek.Sunday    => false
    };

Cette méthode est correcte, mais elle est répétitive.That method is correct, but it's repetitious. On peut la simplifier comme dans le code suivant :You can simplify it, as shown in the following code:

private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Saturday => false,
        DayOfWeek.Sunday => false,
        _ => true
    };

Ensuite, ajoutons une fonction similaire pour catégoriser l’heure dans les blocs :Next, add a similar function to categorize the time into the blocks:

private enum TimeBand
{
    MorningRush,
    Daytime,
    EveningRush,
    Overnight
}

private static TimeBand GetTimeBand(DateTime timeOfToll) =>
    timeOfToll.Hour switch
    {
        < 6 or > 19 => TimeBand.Overnight,
        < 10 => TimeBand.MorningRush,
        < 16 => TimeBand.Daytime,
        _ => TimeBand.EveningRush,
    };

Vous ajoutez un privé enum pour convertir chaque plage horaire en valeur discrète.You add a private enum to convert each range of time to a discrete value. Ensuite, la GetTimeBand méthode utilise des modèles relationnels, et des conjonctives ou des modèles, tous deux ajoutés en C# 9,0.Then, the GetTimeBand method uses relational patterns, and conjunctive or patterns, both added in C# 9.0. Le modèle relationnel vous permet de tester une valeur numérique à l’aide < de,, > <= ou >= .The relational pattern lets you test a numeric value using <, >, <=, or >=. Le or modèle teste si une expression correspond à un ou plusieurs modèles.The or pattern tests if an expression matches one or more patterns. Vous pouvez également utiliser un and modèle pour vous assurer qu’une expression correspond à deux modèles distincts et un not modèle pour vérifier qu’une expression ne correspond pas à un modèle.You can also use an and pattern to ensure that an expression matches two distinct patterns, and a not pattern to test that an expression doesn't match a pattern.

Maintenant que nous avons créé ces méthodes, utilisons une autre expression switch avec le modèle de tuple pour calculer le multiplicateur tarifaire.After you create those methods, you can use another switch expression with the tuple pattern to calculate the pricing premium. On pourrait concevoir une expression switch comportant les 16 branches :You could build a switch expression with all 16 arms:

public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true) => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime, true) => 1.50m,
        (true, TimeBand.Daytime, false) => 1.50m,
        (true, TimeBand.EveningRush, true) => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight, true) => 0.75m,
        (true, TimeBand.Overnight, false) => 0.75m,
        (false, TimeBand.MorningRush, true) => 1.00m,
        (false, TimeBand.MorningRush, false) => 1.00m,
        (false, TimeBand.Daytime, true) => 1.00m,
        (false, TimeBand.Daytime, false) => 1.00m,
        (false, TimeBand.EveningRush, true) => 1.00m,
        (false, TimeBand.EveningRush, false) => 1.00m,
        (false, TimeBand.Overnight, true) => 1.00m,
        (false, TimeBand.Overnight, false) => 1.00m,
    };

Le code ci-dessus fonctionne, mais on peut le simplifier.The above code works, but it can be simplified. Les huit combinaisons du weekend correspondent au même péage.All eight combinations for the weekend have the same toll. Elles sont remplaçables par la ligne suivante :You can replace all eight with the following line:

(false, _, _) => 1.0m,

Le trafic entrant et le trafic sortant ont le même multiplicateur pendant la journée en semaine et pendant la nuit.Both inbound and outbound traffic have the same multiplier during the weekday daytime and overnight hours. Ces quatre branches switch peuvent être remplacées par les deux lignes suivantes :Those four switch arms can be replaced with the following two lines:

(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _)   => 1.5m,

Après ces deux modifications, le code devrait se présenter ainsi :The code should look like the following code after those two changes:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true)  => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime,     _)     => 1.50m,
        (true, TimeBand.EveningRush, true)  => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight,   _)     => 0.75m,
        (false, _,                   _)     => 1.00m,
    };

Enfin, on peut supprimer les deux branches des heures de pointe au tarif normal,Finally, you can remove the two rush hour times that pay the regular price. ce qui permet de remplacer false par un discard (_) dans la branche switch finale.Once you remove those arms, you can replace the false with a discard (_) in the final switch arm. On obtient la méthode suivante une fois terminée :You'll have the following finished method:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.Overnight, _) => 0.75m,
        (true, TimeBand.Daytime, _) => 1.5m,
        (true, TimeBand.MorningRush, true) => 2.0m,
        (true, TimeBand.EveningRush, false) => 2.0m,
        _ => 1.0m,
    };

Cet exemple met en évidence un des avantages des critères spéciaux : les branches du modèle sont évaluées dans l’ordre.This example highlights one of the advantages of pattern matching: the pattern branches are evaluated in order. Si vous les réorganisez de telle sorte qu’une branche antérieure gère un des cas suivants, le compilateur vous avertit que le code est inaccessible.If you rearrange them so that an earlier branch handles one of your later cases, the compiler warns you about the unreachable code. Grâce à ces règles de langage, nous avons pu effectuer facilement les simplifications précédentes sans craindre de modifier le code.Those language rules made it easier to do the preceding simplifications with confidence that the code didn't change.

Les critères spéciaux rendent certains types de code plus lisibles et offrent une alternative aux techniques orientées objet quand vous ne pouvez pas ajouter de code à vos classes.Pattern matching makes some types of code more readable and offers an alternative to object-oriented techniques when you can't add code to your classes. Le cloud est à l’origine d’une séparation entre les données et les fonctionnalités.The cloud is causing data and functionality to live apart. La forme des données et les opérations effectuées sur ces dernières ne sont pas nécessairement décrites ensemble.The shape of the data and the operations on it aren't necessarily described together. Dans ce tutoriel, nous avons exploité les données existantes d’une manière totalement différente de leur fonction d’origine.In this tutorial, you consumed existing data in entirely different ways from its original function. Les critères spéciaux offrent la possibilité d’écrire des fonctionnalités qui remplacent ces types, même sans pouvoir les étendre.Pattern matching gave you the ability to write functionality that overrode those types, even though you couldn't extend them.

Étapes suivantesNext steps

Vous pouvez télécharger le code terminé dans référentiel GitHub dotnet/samples.You can download the finished code from the dotnet/samples GitHub repository. Explorez les modèles par vous-même et ajoutez cette technique à vos activités de codage régulières.Explore patterns on your own and add this technique into your regular coding activities. Ces techniques représentent une autre façon d’aborder les problèmes et de créer de nouvelles fonctionnalités.Learning these techniques gives you another way to approach problems and create new functionality.