Nouveautés de C# 7What's new in C# 7

C# 7 ajoute un certain nombre de nouvelles fonctionnalités au langage C# :C# 7 adds a number of new features to the C# language:

  • Variables outout variables
    • Vous pouvez déclarer des valeurs out inline comme arguments de la méthode dans laquelle elles sont utilisées.You can declare out values inline as arguments to the method where they are used.
  • TuplesTuples
    • Vous pouvez créer des types légers et sans nom qui contiennent plusieurs champs publics.You can create lightweight, unnamed types that contain multiple public fields. Les compilateurs et les outils de l’IDE comprennent la sémantique de ces types.Compilers and IDE tools understand the semantics of these types.
  • Éléments ignorésDiscards
    • Les éléments ignorés sont les variables temporaires en écriture seule utilisées dans les attributions quand vous ne vous souciez pas de la valeur assignée.Discards are temporary, write-only variables used in assignments when you don't care about the value assigned. Ils sont particulièrement utiles lors de la déconstruction de tuples et de types définis par l’utilisateur, ainsi que lors de l’appel de méthodes avec des paramètres out.They are particularly useful when deconstructing tuples and user-defined types, as well as when calling methods with out parameters.
  • Critères spéciauxPattern Matching
    • Vous pouvez créer une logique de branchement basée sur des types arbitraires et les valeurs des membres de ces types.You can create branching logic based on arbitrary types and values of the members of those types.
  • Variables locales et retours refref locals and returns
    • Les arguments et les variables locales de méthode peuvent être des références à un autre stockage.Method arguments and local variables can be references to other storage.
  • Fonctions localesLocal Functions
    • Vous pouvez imbriquer des fonctions dans d’autres fonctions afin de limiter leur portée et leur visibilité.You can nest functions inside other functions to limit their scope and visibility.
  • Autres membres expression-bodiedMore expression-bodied members
    • La liste des membres pouvant être créés à l’aide d’expressions s’est allongée.The list of members that can be authored using expressions has grown.
  • Expressions throwthrow Expressions
    • Vous pouvez lever des exceptions dans les constructions de code qui n’étaient pas autorisées auparavant, car throw était une instruction.You can throw exceptions in code constructs that previously were not allowed because throw was a statement.
  • Types de retour async généralisésGeneralized async return types
    • Les méthodes déclarées avec le modificateur async peuvent retourner d’autres types en plus de Task et de Task<T>.Methods declared with the async modifier can return other types in addition to Task and Task<T>.
  • Améliorations de la syntaxe littérale numériqueNumeric literal syntax improvements
    • De nouveaux jetons améliorent la lisibilité des constantes numériques.New tokens improve readability for numeric constants.

Le reste de cette rubrique traite de chacune de ces fonctionnalités.The remainder of this topic discusses each of the features. Vous découvrirez la logique de chacune d’elles.For each feature, you'll learn the reasoning behind it. Vous allez apprendre leur syntaxe.You'll learn the syntax. Grâces à des exemples de scénarios utilisant les nouvelles fonctionnalités, vous gagnerez en productivité en tant que développeur.You'll see some sample scenarios where using the new feature will make you more productive as a developer.

Variables outout variables

La syntaxe existante qui prend en charge les paramètres out a été améliorée dans cette version.The existing syntax that supports out parameters has been improved in this version.

Auparavant, vous deviez séparer la déclaration de la variable out et son initialisation en deux instructions distinctes :Previously, you would need to separate the declaration of the out variable and its initialization into two different statements:

int numericResult;
if (int.TryParse(input, out numericResult))
    WriteLine(numericResult);
else
    WriteLine("Could not parse input");

Vous pouvez désormais déclarer des variables out dans la liste d’arguments d’un appel de méthode, au lieu d’écrire une instruction de déclaration distincte :You can now declare out variables in the argument list of a method call, rather than writing a separate declaration statement:

if (int.TryParse(input, out int result))
    WriteLine(result);
else
    WriteLine("Could not parse input");

Par souci de clarté, vous voulez peut-être spécifier le type de la variable out, comme indiqué ci-dessus.You may want to specify the type of the out variable for clarity, as shown above. Toutefois, le langage prend en charge l’utilisation d’une variable locale implicitement typée :However, the language does support using an implicitly typed local variable:

if (int.TryParse(input, out var answer))
    WriteLine(answer);
else
    WriteLine("Could not parse input");
  • Le code est plus facile à lire.The code is easier to read.
    • Vous déclarez la variable out à l’endroit où vous l’utilisez, et non pas sur une autre ligne au-dessus.You declare the out variable where you use it, not on another line above.
  • Il n’est pas nécessaire d’assigner une valeur initiale.No need to assign an initial value.
    • En déclarant la variable out à l’endroit où elle est utilisée dans un appel de méthode, vous ne pouvez pas l’utiliser accidentellement avant qu’elle soit assignée.By declaring the out variable where it is used in a method call, you can't accidentally use it before it is assigned.

L’utilisation la plus courante de cette fonctionnalité est le modèle Try.The most common use for this feature will be the Try pattern. Dans ce modèle, une méthode retourne un bool indiquant la réussite ou l’échec, et une variable out qui donne le résultat en cas de réussite de la méthode.In this pattern, a method returns a bool indicating success or failure and an out variable that provides the result if the method succeeds.

Quand vous utilisez la déclaration de variable out, la variable déclarée « fuit » dans la portée externe de l’instruction if.When using the out variable declaration, the declared variable "leaks" into the outer scope of the if statement. Cela vous permet d’utiliser la variable par la suite :This allows you to use the variable afterwards:

if (!int.TryParse(input, out int result))
{    
    return null;
}

return result;

TuplesTuples

Note

Les nouvelles fonctionnalités des tuples exigent les types ValueTuple.The new tuples features require the ValueTuple types. Vous devez ajouter le package NuGet System.ValueTuple pour pouvoir l’utiliser sur les plateformes qui n’incluent pas les types.You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

Ces fonctionnalités sont semblables à celles d’autres langages qui reposent sur les types fournis dans le framework.This is similar to other language features that rely on types delivered in the framework. async et await qui reposent sur l’interface INotifyCompletion, et LINQ qui repose sur IEnumerable<T> en sont des exemples.Example include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. Toutefois, le mécanisme de remise change à mesure que le .NET dépend de moins en moins de la plateforme.However, the delivery mechanism is changing as .NET is becoming more platform independent. Le .NET Framework n’est pas toujours émis à la même cadence que le compilateur de langage.The .NET Framework may not always ship on the same cadence as the language compiler. Quand les nouvelles fonctionnalités de langage reposent sur de nouveaux types, ces types sont disponibles sous la forme de packages NuGet au moment de l’émission des fonctionnalités de langage.When new language features rely on new types, those types will be available as NuGet packages when the language features ship. À mesure que ces nouveaux types sont ajoutés à l’API .NET Standard et remis dans le cadre du framework, les packages NuGet ne sont plus obligatoires.As these new types get added to the .NET Standard API and delivered as part of the framework, the NuGet package requirement will be removed.

C# fournit une syntaxe complète pour les classes et les structs, utilisée pour expliquer l’intention de votre conception.C# provides a rich syntax for classes and structs that is used to explain your design intent. Cependant, cette syntaxe complète nécessite parfois un travail supplémentaire avec peu d’avantages.But sometimes that rich syntax requires extra work with minimal benefit. Vous pouvez souvent écrire des méthodes qui nécessitent une structure simple contenant plusieurs éléments de données.You may often write methods that need a simple structure containing more than one data element. Pour prendre en charge ces scénarios, des tuples ont été ajoutées à C#.To support these scenarios tuples were added to C#. Les tuples sont des structures de données légères contenant plusieurs champs pour représenter les membres de données.Tuples are lightweight data structures that contain multiple fields to represent the data members. Les champs ne sont pas validés, et vous ne pouvez pas définir vos propres méthodes.The fields are not validated, and you cannot define your own methods

Note

Les tuples étaient disponibles avant C# 7, mais ils n’étaient pas efficaces et n’avaient aucune prise en charge du langage.Tuples were available before C# 7, but they were inefficient and had no language support. Cela signifiait que les éléments tuples pouvaient uniquement être référencés comme Item1, Item2, et ainsi de suite.This meant that tuple elements could only be referenced as Item1, Item2 and so on. C# 7 introduit la prise en charge du langage pour les tuples, ce qui permet d’utiliser des noms sémantiques pour les champs d’un tuple avec de nouveaux types tuple plus efficaces.C# 7 introduces language support for tuples, which enables semantic names for the fields of a tuple using new, more efficient tuple types.

Vous pouvez créer un tuple en assignant chaque membre à une valeur :You can create a tuple by assigning each member to a value:

var letters = ("a", "b");

Cette assignation crée un tuple dont les membres sont Item1 et Item2, que vous pouvez utiliser de la même façon que Tuple. Vous pouvez modifier la syntaxe pour créer un tuple qui fournit des noms sémantiques à chacun des membres du tuple :That assignment creates a tuple whose members are Item1 and Item2, which you can use in the same way as Tuple You can change the syntax to create a tuple that provides semantic names to each of the members of the tuple:

(string Alpha, string Beta) namedLetters = ("a", "b");

Le tuple namedLetters contient des champs appelés Alpha et Beta.The namedLetters tuple contains fields referred to as Alpha and Beta. Ces noms n’existent qu’au moment de la compilation et ne sont pas conservés par exemple lors de l’inspection du tuple à l’aide de la réflexion lors de l’exécution.Those names exist only at compile time and are not preserved for example when inspecting the tuple using reflection at runtime.

Dans une assignation de tuple, vous pouvez également spécifier les noms des champs dans la partie droite :In a tuple assignment, you can also specify the names of the fields on the right-hand side of the assignment:

var alphabetStart = (Alpha: "a", Beta: "b");

Vous pouvez spécifier des noms pour les champs dans la partie gauche et dans la partie droite de l’assignation :You can specify names for the fields on both the left and right-hand side of the assignment:

(string First, string Second) firstLetters = (Alpha: "a", Beta: "b");

La ligne ci-dessus génère un avertissement, CS8123, vous indiquant que les noms dans la partie droite de l’assignation, Alpha et Beta, sont ignorés, car ils sont en conflit avec les noms dans la partie gauche, First et Second.The line above generates a warning, CS8123, telling you that the names on the right side of the assignment, Alpha and Beta are ignored because they conflict with the names on the left side, First and Second.

Les exemples ci-dessus illustrent la syntaxe de base permettant de déclarer des tuples.The examples above show the basic syntax to declare tuples. Ces derniers sont surtout utiles comme types de retour pour les méthodes private et internal.Tuples are most useful as return types for private and internal methods. Ils fournissent une syntaxe simple pour permettre à ces méthodes de retourner plusieurs valeurs discrètes : vous enregistrez le travail de création d’un class ou d’un struct qui définit le type retourné.Tuples provide a simple syntax for those methods to return multiple discrete values: You save the work of authoring a class or a struct that defines the type returned. Il n’est pas nécessaire de créer un nouveau type.There is no need for creating a new type.

Il est plus efficace et plus productif de créer un tuple.Creating a tuple is more efficient and more productive. Il s’agit d’une syntaxe légère et plus simple utilisée pour définir une structure de données qui comporte plusieurs valeurs.It is a simpler, lightweight syntax to define a data structure that carries more than one value. L’exemple de méthode ci-dessous retourne les valeurs minimale et maximale trouvées dans une séquence d’entiers :The example method below returns the minimum and maximum values found in a sequence of integers:

private static (int Max, int Min) Range(IEnumerable<int> numbers)
{
    int min = int.MaxValue;
    int max = int.MinValue;
    foreach(var n in numbers)
    {
        min = (n < min) ? n : min;
        max = (n > max) ? n : max;
    }
    return (max, min);
}

En utilisant les tuples de cette façon, vous bénéficiez de plusieurs avantages :Using tuples in this way offers several advantages:

  • Vous enregistrez le travail de création d’un class ou d’un struct qui définit le type retourné.You save the work of authoring a class or a struct that defines the type returned.
  • Vous n’avez pas besoin de créer un type.You do not need to create new type.
  • Les améliorations du langage suppriment la nécessité d’appeler les méthodes Create<T1>(T1).The language enhancements removes the need to call the Create<T1>(T1) methods.

La déclaration pour la méthode fournit les noms des champs du tuple qui est retourné.The declaration for the method provides the names for the fields of the tuple that is returned. Quand vous appelez la méthode, la valeur de retour est un tuple dont les champs sont Max et Min :When you call the method, the return value is a tuple whose fields are Max and Min:

var range = Range(numbers);

Dans certains cas, vous pouvez souhaiter décompresser les membres d’un tuple qui ont été retournés à partir d’une méthode.There may be times when you want to unpackage the members of a tuple that were returned from a method. Pour ce faire, déclarez des variables distinctes pour chacune des valeurs dans le tuple.You can do that by declaring separate variables for each of the values in the tuple. Cette opération est appelée déconstruction du tuple :This is called deconstructing the tuple:

(int max, int min) = Range(numbers);

Vous pouvez également fournir une déconstruction similaire pour tout type dans le .NET.You can also provide a similar deconstruction for any type in .NET. Pour ce faire, écrivez une méthode Deconstruct comme un membre de la classe.This is done by writing a Deconstruct method as a member of the class. Cette méthode Deconstruct fournit un ensemble d’arguments out pour chacune des propriétés que vous voulez extraire.That Deconstruct method provides a set of out arguments for each of the properties you want to extract. Prenons la classe Point suivante qui fournit une méthode de déconstructeur qui extrait les coordonnées X et Y :Consider this Point class that provides a deconstructor method that extracts the X and Y coordinates:

public class Point
{
    public Point(double x, double y)
    {
        this.X = x;
        this.Y = y;
    }

    public double X { get; }
    public double Y { get; }

    public void Deconstruct(out double x, out double y)
    {
        x = this.X;
        y = this.Y;
    }
}

Vous pouvez extraire des champs individuels en assignant un tuple à un Point :You can extract the individual fields by assigning a tuple to a Point:

var p = new Point(3.14, 2.71);
(double X, double Y) = p;

Vous n’êtes pas lié par les noms définis dans la méthode Deconstruct.You are not bound by the names defined in the Deconstruct method. Vous pouvez renommer les variables d’extraction dans le cadre de l’assignation :You can rename the extract variables as part of the assignment:

(double horizontalDistance, double verticalDistance) = p;

La rubrique relative au tuples vous permet d’étudier les tuples plus en détail.You can learn more in depth about tuples in the tuples topic.

Éléments ignorésDiscards

Souvent, lors de la déconstruction d’un tuple ou de l’appel d’une méthode avec des paramètres out, vous devez définir une variable dont la valeur ne vous importe pas et que vous ne prévoyez pas d’utiliser.Often when deconstructing a tuple or calling a method with out parameters, you're forced to define a variable whose value you don't care about and don't intend to use. C# ajoute la prise en charge des éléments ignorés pour gérer ce scénario.C# adds support for discards to handle this scenario. Un élément ignoré est une variable en écriture seule dont le nom est _ (caractère de soulignement) ; vous pouvez assigner toutes les valeurs que vous souhaitez ignorer à la variable unique.A discard is a write-only variable whose name is _ (the underscore character); you can assign all of the values that you intend to discard to the single variable. Un élément ignoré est semblable à une variable non assignée ; en dehors de l’instruction d’assignation, l’élément ignoré ne peut pas être utilisé dans le code.A discard is like an unassigned variable; apart from the assignment statement, the discard can't be used in code.

Les éléments ignorés sont pris en charge dans les scénarios suivants :Discards are supported in the following scenarios:

  • Lors de la déconstruction de tuples ou de types définis par l’utilisateur.When deconstructing tuples or user-defined types.

  • Lors d’appels à des méthodes avec des paramètres out.When calling methods with out parameters.

  • Dans une opération de critères spéciaux avec les instructions is et switch.In a pattern matching operation with the is and switch statements.

  • Comme un identificateur autonome quand vous voulez explicitement identifier la valeur d’une assignation comme un élément ignoré.As a standalone identifier when you want to explicitly identify the value of an assignment as a discard.

L’exemple suivant définit une méthode QueryCityDataForYears qui retourne un tuple à 6 composants qui contient des données pour une ville au cours de deux années différentes.The following example defines a QueryCityDataForYears method that returns a 6-tuple that contains a data for a city for two different years. L’appel de méthode dans l’exemple s’intéresse uniquement à deux valeurs de population retournées par la méthode et traite par conséquent les valeurs restantes dans le tuple comme des éléments ignorés lors de la déconstruction du tuple.The method call in the example is concerned only with the two population values returned by the method and so treats the remaining values in the tuple as discards when it deconstructs the tuple.

using System;
using System.Collections.Generic;

public class Example
{
   public static void Main()
   {
       var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

       Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
   }
   
   private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
   {
      int population1 = 0, population2 = 0;
      double area = 0;
      
      if (name == "New York City") {
         area = 468.48; 
         if (year1 == 1960) {
            population1 = 7781984;
         }
         if (year2 == 2010) {
            population2 = 8175133;
         }
      return (name, area, year1, population1, year2, population2);
      }

      return ("", 0, 0, 0, 0, 0);
   }
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

Pour plus d’informations, consultez Éléments ignorés.For more information, see Discards.

Critères spéciauxPattern matching

Les critères spéciaux constituent une fonctionnalité qui vous permet d’implémenter la distribution de méthodes sur des propriétés autres que le type d’un objet.Pattern matching is a feature that allows you to implement method dispatch on properties other than the type of an object. Vous êtes probablement déjà familiarisé avec la distribution de méthodes en fonction du type d’un objet.You're probably already familiar with method dispatch based on the type of an object. Dans la programmation orientée objet, les méthodes virtuelles et override fournissent une syntaxe du langage permettant d’implémenter la distribution de méthodes basées sur le type d’un objet.In Object Oriented programming, virtual and override methods provide language syntax to implement method dispatching based on an object's type. Les classes de base et dérivées offrent des implémentations différentes.Base and Derived classes provide different implementations. Les expressions de critères spéciaux étendent ce concept de manière à vous permettre d’implémenter facilement des modèles de distribution similaires pour les types et les éléments de données qui ne sont pas liés au moyen d’une hiérarchie d’héritage.Pattern matching expressions extend this concept so that you can easily implement similar dispatch patterns for types and data elements that are not related through an inheritance hierarchy.

Les critères spéciaux prennent en charge les expressions is et les expressions switch.Pattern matching supports is expressions and switch expressions. L’une ou l’autre permettent d’inspecter un objet et ses propriétés afin de déterminer s’il correspond au modèle recherché.Each enables inspecting an object and its properties to determine if that object satisfies the sought pattern. Vous utilisez le mot clé when pour spécifier des règles supplémentaires pour le modèle.You use the when keyword to specify additional rules to the pattern.

Expression isis expression

L’expression de modèle is étend l’opérateur is classique pour interroger un objet au-delà de son type.The is pattern expression extends the familiar is operator to query an object beyond its type.

Commençons par un scénario simple.Let's start with a simple scenario. Nous allons ajouter à ce scénario des fonctionnalités qui illustrent la façon dont les expressions de critères spéciaux facilitent les algorithmes qui fonctionnent avec les types non apparentés.We'll add capabilities to this scenario that demonstrate how pattern matching expressions make algorithms that work with unrelated types easy. Nous allons commencer par une méthode qui calcule la somme d’un certain nombre de lancers de dés :We'll start with a method that computes the sum of a number of die rolls:

public static int DiceSum(IEnumerable<int> values)
{
    return values.Sum();
}

Vous constaterez peut-être rapidement que vous devez calculer la somme de lancers de dés où certains des lancers sont effectués avec plusieurs dés.You might quickly find that you need to find the sum of die rolls where some of the rolls are made with multiple dice (dice is the plural of die). Une partie de la séquence d’entrée peut être constituée de plusieurs résultats au lieu d’un seul nombre :Part of the input sequence may be multiple results instead of a single number:

public static int DiceSum2(IEnumerable<object> values)
{
    var sum = 0;
    foreach(var item in values)
    {
        if (item is int val)
            sum += val;
        else if (item is IEnumerable<object> subList)
            sum += DiceSum2(subList);
    }
    return sum;
}

L’expression de modèle is fonctionne très bien dans ce scénario.The is pattern expression works quite well in this scenario. Dans le cadre de la vérification du type, vous écrivez une initialisation de variable.As part of checking the type, you write a variable initialization. Cela crée une variable du type de runtime validé.This creates a new variable of the validated runtime type.

À mesure que vous continuerez à développer ces scénarios, vous constaterez peut-être que vous générez plus d’instructions if et else if.As you keep extending these scenarios, you may find that you build more if and else if statements. Quand cela deviendra peu pratique, vous voudrez probablement passer à des expressions de modèle switch.Once that becomes unwieldy, you'll likely want to switch to switch pattern expressions.

Mises à jour d’instructions switchswitch statement updates

L’expression de correspondance a une syntaxe familière, basée sur l’instruction switch qui fait déjà partie du langage C#.The match expression has a familiar syntax, based on the switch statement already part of the C# language. Traduisons le code existant pour utiliser une expression de correspondance avant d’ajouter de nouveaux cas :Let's translate the existing code to use a match expression before adding new cases:

public static int DiceSum3(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case int val:
                sum += val;
                break;
            case IEnumerable<object> subList:
                sum += DiceSum3(subList);
                break;
        }
    }
    return sum;
}

Les expressions de correspondance ont une syntaxe légèrement différente des expressions is, dans laquelle vous déclarez le type et la variable au début de l’expression case.The match expressions have a slightly different syntax than the is expressions, where you declare the type and variable at the beginning of the case expression.

Les expressions de correspondance prennent également en charge les constantes.The match expressions also support constants. Cela peut faire gagner du temps en éliminant les cas simples :This can save time by factoring out simple cases:

public static int DiceSum4(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case 0:
                break;
            case int val:
                sum += val;
                break;
            case IEnumerable<object> subList when subList.Any():
                sum += DiceSum4(subList);
                break;
            case IEnumerable<object> subList:
                break;
            case null:
                break;
            default:
                throw new InvalidOperationException("unknown item type");
        }
    }
    return sum;
}

Le code ci-dessus ajoute des cas pour 0 comme un cas particulier de int, et null comme un cas particulier quand il n’y a aucune entrée.The code above adds cases for 0 as a special case of int, and null as a special case when there is no input. Cela illustre une nouvelle fonctionnalité importante des expressions de modèle switch : l’ordre des expressions case a maintenant de l’importance.This demonstrates one important new feature in switch pattern expressions: the order of the case expressions now matters. Le cas 0 doit apparaître avant le cas général int.The 0 case must appear before the general int case. Sinon, le premier modèle à mettre en correspondance serait le cas int, même si la valeur est 0.Otherwise, the first pattern to match would be the int case, even when the value is 0. Si vous classez par mégarde des expressions de correspondance de sorte qu’un cas ultérieur a déjà été traité, le compilateur le signale et génère une erreur.If you accidentally order match expressions such that a later case has already been handled, the compiler will flag that and generate an error.

Ce même comportement active le cas particulier pour une séquence d’entrée vide.This same behavior enables the special case for an empty input sequence. Vous pouvez voir que le cas pour un élément IEnumerable qui contient des éléments doit apparaître avant le cas général IEnumerable.You can see that the case for an IEnumerable item that has elements must appear before the general IEnumerable case.

Cette version a également ajouté un cas default.This version has also added a default case. Le cas default est toujours évalué en dernier, quel que soit l’ordre dans lequel il apparaît dans la source.The default case is always evaluated last, regardless of the order it appears in the source. C’est pourquoi la convention est de placer le cas default en dernier.For that reason, convention is to put the default case last.

Pour finir, ajoutons un dernier case pour un nouveau style de dé.Finally, let's add one last case for a new style of die. Certains jeux utilisent des dés percentiles pour représenter des plages de nombres plus importantes.Some games use percentile dice to represent larger ranges of numbers.

Note

Deux dés percentiles à 10 faces peuvent représenter chaque nombre compris entre 0 et 99.Two 10-sided percentile dice can represent every number from 0 through 99. Un dé a des faces marquées 00, 10, 20, ... 90.One die has sides labelled 00, 10, 20, ... 90. L’autre dé a des faces marquées 0, 1, 2, ... 9.The other die has sides labeled 0, 1, 2, ... 9. Additionnez les valeurs des deux dés. Vous pouvez alors obtenir n’importe quel nombre compris entre 0 à 99.Add the two die values together and you can get every number from 0 through 99.

Pour ajouter ce genre de dé à votre collection, commencez par définir un type pour représenter le dé percentile.To add this kind of die to your collection, first define a type to represent the percentile dice. La propriété TensDigit stocke les valeurs 0, 10, 20, jusqu’à 90 :The TensDigit property stores values 0, 10, 20, up to 90:

public struct PercentileDice
{
    public int OnesDigit { get; }
    public int TensDigit { get; }

    public PercentileDice(int tensDigit, int onesDigit)
    {
        this.OnesDigit = onesDigit;
        this.TensDigit = tensDigit;
    }
}

Ajoutez ensuite une expression de correspondance case pour le nouveau type :Then, add a case match expression for the new type:

public static int DiceSum5(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case 0:
                break;
            case int val:
                sum += val;
                break;
            case PercentileDice dice:
                sum += dice.TensDigit + dice.OnesDigit;
                break;
            case IEnumerable<object> subList when subList.Any():
                sum += DiceSum5(subList);
                break;
            case IEnumerable<object> subList:
                break;
            case null:
                break;
            default:
                throw new InvalidOperationException("unknown item type");
        }
    }
    return sum;
}

La nouvelle syntaxe pour les expressions de critères spéciaux facilite la création d’algorithmes de distribution basés sur le type d’un objet, ou d’autres propriétés, en utilisant une syntaxe claire et concise.The new syntax for pattern matching expressions makes it easier to create dispatch algorithms based on an object's type, or other properties, using a clear and concise syntax. Les expressions de critères spéciaux permettent ces constructions sur les types de données qui ne sont pas liés par héritage.Pattern matching expressions enable these constructs on data types that are unrelated by inheritance.

Pour plus d’informations sur les critères spéciaux, consultez la rubrique consacrée aux critères spéciaux en C#.You can learn more about pattern matching in the topic dedicated to pattern matching in C#.

Variables locales et retours refRef locals and returns

Cette fonctionnalité active les algorithmes qui utilisent et retournent des références à des variables définies ailleurs.This feature enables algorithms that use and return references to variables defined elsewhere. C’est le cas, par exemple, lors de la recherche d’un emplacement unique comportant certaines caractéristiques dans une matrice de grande taille.One example is working with large matrices, and finding a single location with certain characteristics. Une méthode retournerait les deux index pour un seul emplacement dans la matrice :One method would return the two indices for a single location in the matrix:

public static (int i, int j) Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return (i, j);
    return (-1, -1); // Not found
}

Ce code présente de nombreux problèmes.There are many issues with this code. Tout d’abord, il s’agit d’une méthode publique qui retourne un tuple.First of all, it's a public method that's returning a tuple. Le langage prend en charge ce fait, mais les types définis par l’utilisateur (les classes ou les structs) conviennent mieux pour les API publiques.The language supports this, but user defined types (either classes or structs) are preferred for public APIs.

Ensuite, cette méthode retourne les index à l’élément dans la matrice.Second, this method is returning the indices to the item in the matrix. Cela conduit les appelants à écrire du code qui utilise ces index pour déréférencer la matrice et modifier un seul élément :That leads callers to write code that uses those indices to dereference the matrix and modify a single element:

var indices = MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(indices);
matrix[indices.i, indices.j] = 24;

Il est préférable d’écrire une méthode qui retourne une référence à l’élément de la matrice que vous voulez changer.You'd rather write a method that returns a reference to the element of the matrix that you want to change. Pour y parvenir, la seule solution consiste à utiliser du code unsafe et à retourner un pointeur vers un int dans des versions précédentes.You could only accomplish this by using unsafe code and returning a pointer to an int in previous versions.

Examinons en détail une série de changements pour illustrer la fonctionnalité de variable locale de référence et montrer comment créer une méthode qui retourne une référence à un stockage interne.Let's walk through a series of changes to demonstrate the ref local feature and show how to create a method that returns a reference to internal storage. Au cours du processus, vous allez apprendre les règles de la fonctionnalité de retour ref et de variables locales ref qui vous évite de l’utiliser incorrectement par mégarde.Along the way, you'll learn the rules of the ref return and ref local feature that protects you from accidentally misusing it.

Commencez par modifier la déclaration de méthode Find pour qu’elle retourne un ref int au lieu d’un tuple.Start by modifying the Find method declaration so that it returns a ref int instead of a tuple. Ensuite, modifiez l’instruction return de sorte qu’elle retourne la valeur stockée dans la matrice plutôt que les deux index :Then, modify the return statement so it returns the value stored in the matrix instead of the two indices:

// Note that this won't compile. 
// Method declaration indicates ref return,
// but return statement specifies a value return.
public static ref int Find2(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return matrix[i, j];
    throw new InvalidOperationException("Not found");
}

Quand vous déclarez qu’une méthode retourne une variable ref, vous devez également ajouter le mot clé ref à chaque instruction return.When you declare that a method returns a ref variable, you must also add the ref keyword to each return statement. Cela indique un retour par référence, et permet aux développeurs qui lisent le code ultérieurement de ne pas oublier que la méthode effectue un retour par référence :That indicates return by reference, and helps developers reading the code later remember that the method returns by reference:

public static ref int Find3(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

Maintenant que la méthode retourne une référence à la valeur entière dans la matrice, vous devez modifier l’emplacement où elle est appelée.Now that the method returns a reference to the integer value in the matrix, you need to modify where it's called. La déclaration var signifie que valItem est désormais un int au lieu d’un tuple :The var declaration means that valItem is now an int rather than a tuple:

var valItem = MatrixSearch.Find3(matrix, (val) => val == 42);
Console.WriteLine(valItem);
valItem = 24;
Console.WriteLine(matrix[4, 2]);

La deuxième instruction WriteLine dans l’exemple ci-dessus affiche la valeur 42, et non la valeur 24.The second WriteLine statement in the example above prints out the value 42, not 24. La variable valItem est un int, et non un ref int.The variable valItem is an int, not a ref int. Le mot clé var permet au compilateur de spécifier le type, mais il n’ajoutera pas implicitement le modificateur ref.The var keyword enables the compiler to specify the type, but will not implicitly add the ref modifier. Au lieu de cela, la valeur référencée par ref return est copiée dans la variable dans la partie gauche de l’assignation.Instead, the value referred to by the ref return is copied to the variable on the left-hand side of the assignment. La variable n’est pas une variable ref locale.The variable is not a ref local.

Pour obtenir le résultat souhaité, vous devez ajouter le ref modificateur à la déclaration de variable locale pour faire de la variable une référence quand la valeur de retour est une référence :In order to get the result you want, you need to add the ref modifier to the local variable declaration to make the variable a reference when the return value is a reference:

ref var item = ref MatrixSearch.Find3(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);

À présent, la deuxième instruction WriteLine dans l’exemple ci-dessus affiche la valeur 24, ce qui indique que le stockage dans la matrice a été modifié.Now, the second WriteLine statement in the example above will print out the value 24, indicating that the storage in the matrix has been modified. La variable locale a été déclarée avec le modificateur ref, et elle prendra un retour ref.The local variable has been declared with the ref modifier, and it will take a ref return. Vous devez initialiser une variable ref au moment de sa déclaration : vous ne pouvez pas séparer la déclaration et l’initialisation.You must initialize a ref variable when it is declared, you cannot split the declaration and the initialization.

Le langage C# a trois autres règles qui vous protègent contre une mauvaise utilisation des variables locales et des retours ref :The C# language has three other rules that protect you from misusing the ref locals and returns:

  • Vous ne pouvez pas affecter une valeur de retour de méthode standard à une variable locale ref.You cannot assign a standard method return value to a ref local variable.
    • Cela rejette les instructions telles que ref int i = sequence.Count();.That disallows statements like ref int i = sequence.Count();
  • Vous ne pouvez pas retourner un ref à une variable dont la durée de vie ne s’étend pas au-delà de l’exécution de la méthode.You cannot return a ref to a variable whose lifetime does not extend beyond the execution of the method.
    • Cela signifie que vous ne pouvez pas retourner une référence à une variable locale ni une variable avec une étendue similaire.That means you cannot return a reference to a local variable or a variable with a similar scope.
  • Les variables locales et les retours ref ne peuvent pas être utilisés avec les méthodes Async.ref locals and returns can't be used with async methods.
    • Le compilateur ne peut pas savoir si la variable référencée a été définie à sa valeur finale quand la méthode Async est retournée.The compiler can't know if the referenced variable has been set to its final value when the async method returns.

L’ajout de variables locales ref et de retours ref permet d’utiliser des algorithmes qui sont plus efficaces en évitant la copie de valeurs, ou d’effectuer plusieurs fois des opérations de déréférencement.The addition of ref locals and ref returns enable algorithms that are more efficient by avoiding copying values, or performing dereferencing operations multiple times.

Fonctions localesLocal functions

De nombreuses conceptions pour les classes incluent des méthodes qui sont appelées à partir d’un seul emplacement.Many designs for classes include methods that are called from only one location. Ces méthodes privées supplémentaires maintiennent chaque méthode réduite et focalisée.These additional private methods keep each method small and focused. Toutefois, elles peuvent rendre plus difficile la compréhension d’une classe quand elle est lue pour la première fois.However, they can make it harder to understand a class when reading it the first time. Ces méthodes doivent être comprises en dehors du contexte de l’emplacement d’appel unique.These methods must be understood outside of the context of the single calling location.

Pour ces conceptions, les fonctions locales vous permettent de déclarer des méthodes dans le contexte d’une autre méthode.For those designs, local functions enable you to declare methods inside the context of another method. Il est ainsi plus facile pour les lecteurs de la classe de voir que la méthode locale est appelée uniquement à partir du contexte dans lequel elle a été déclarée.This makes it easier for readers of the class to see that the local method is only called from the context in which is it declared.

Il existe deux cas d’utilisation très courants pour les fonctions locales : les méthodes iterator publiques et les méthodes async publiques.There are two very common use cases for local functions: public iterator methods and public async methods. Ces deux types de méthodes génèrent du code qui signale les erreurs plus tard que ce qu’attendent les programmeurs.Both types of methods generate code that reports errors later than programmers might expect. Dans le cas des méthodes iterator, toute exception est observée uniquement lors de l’appel de code qui énumère la séquence retournée.In the case of iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. Dans le cas des méthodes async, toute exception est observée uniquement quand le Task retourné est attendu.In the case of async methods, any exceptions are only observed when the returned Task is awaited.

Commençons par une méthode iterator :Let's start with an iterator method:

public static IEnumerable<char> AlphabetSubset(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    for (var c = start; c < end; c++)
        yield return c;
}

Examinez le code ci-dessous, qui appelle la méthode iterator de manière incorrecte :Examine the code below that calls the iterator method incorrectly:

var resultSet = Iterator.AlphabetSubset('f', 'a');
Console.WriteLine("iterator created");
foreach (var thing in resultSet)
    Console.Write($"{thing}, ");

L’exception est levée quand resultSet est itéré, pas quand resultSet est créé.The exception is thrown when resultSet is iterated, not when resultSet is created. Dans cet exemple contenu, la plupart des développeurs peuvent diagnostiquer rapidement le problème.In this contained example, most developers could quickly diagnose the problem. Toutefois, dans les codes base de plus grande taille, il est fréquent que le code qui crée un itérateur ne soit pas aussi proche du code qui énumère le résultat.However, in larger codebases, the code that creates an iterator often isn't as close to the code that enumerates the result. Vous pouvez refactoriser le code afin que la méthode publique valide tous les arguments, et qu’une méthode privée génère l’énumération :You can refactor the code so that the public method validates all arguments, and a private method generates the enumeration:

public static IEnumerable<char> AlphabetSubset2(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    return alphabetSubsetImplementation(start, end);
}

private static IEnumerable<char> alphabetSubsetImplementation(char start, char end)
{ 
    for (var c = start; c < end; c++)
        yield return c;
}

Cette version refactorisée lève immédiatement des exceptions, car la méthode publique n’est pas une méthode iterator ; seule la méthode privée utilise la syntaxe yield return.This refactored version will throw exceptions immediately because the public method is not an iterator method; only the private method uses the yield return syntax. Toutefois, cette refactorisation pose deux problèmes potentiels.However, there are potential problems with this refactoring. La méthode privée ne doit être appelée qu’à partir de la méthode d’interface publique, car sinon, toute validation d’argument est ignorée.The private method should only be called from the public interface method, because otherwise all argument validation is skipped. Les lecteurs de la classe doivent le découvrir en lisant l’intégralité de la classe et en recherchant toute autre référence à la méthode alphabetSubsetImplementation.Readers of the class must discover this fact by reading the entire class and searching for any other references to the alphabetSubsetImplementation method.

Vous pourrez rendre cette intention de conception plus claire en déclarant alphabetSubsetImplementation comme une fonction locale à l’intérieur de la méthode d’API publique :You can make that design intent more clear by declaring the alphabetSubsetImplementation as a local function inside the public API method:

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

La version ci-dessus indique clairement que la méthode locale est référencée uniquement dans le contexte de la méthode externe.The version above makes it clear that the local method is referenced only in the context of the outer method. Les règles relatives aux fonctions locales peuvent également garantir qu’un développeur ne peut pas appeler accidentellement la fonction locale à partir d’un autre emplacement dans la classe et ignorer la validation d’argument.The rules for local functions also ensure that a developer can't accidentally call the local function from another location in the class and bypass the argument validation.

Il est possible d’utiliser la même technique avec les méthodes async pour garantir que les exceptions résultant de la validation d’argument sont levées avant le début de la tâche asynchrone :The same technique can be employed with async methods to ensure that exceptions arising from argument validation are thrown before the asynchronous work begins:

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

Note

Certaines des conceptions prises en charge par les fonctions locales peuvent également être effectuées à l’aide d’expressions lambda.Some of the designs that are supported by local functions could also be accomplished using lambda expressions. Si cela vous intéresse, reportez-vous aux informations supplémentaires décrivant ce qui différencie ces deux processus.Those interested can read more about the differences

Autres membres expression-bodiedMore expression-bodied members

C# 6 a introduit les membres expression-bodied pour les fonctions membres, ainsi que des propriétés en lecture seule.C# 6 introduced expression-bodied members for member functions, and read-only properties. C# 7 développe les membres autorisés pouvant être implémentés comme expressions.C# 7 expands the allowed members that can be implemented as expressions. Dans C# 7, vous pouvez implémenter des constructeurs, des finaliseurs ainsi que des accesseurs get et set sur des propriétés et des indexeurs.In C# 7, you can implement constructors, finalizers, and get and set accessors on properties and indexers. Le code suivant présente des exemples de chaque élément :The following code shows examples of each:

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

Note

Cet exemple n’a pas besoin de finaliseur, mais il est présenté pour illustrer la syntaxe.This example does not need a finalizer, but it is shown to demonstrate the syntax. Vous ne devez implémenter un finaliseur dans votre classe que si cela est nécessaire pour libérer des ressources non managées.You should not implement a finalizer in your class unless it is necessary to release unmanaged resources. Vous devez également envisager d’utiliser la classe SafeHandle au lieu de gérer directement les ressources non managées.You should also consider using the SafeHandle class instead of managing unmanaged resources directly.

Ces nouveaux emplacements pour les membres expression-bodied représentent une étape importante pour le langage C# : ces fonctionnalités ont été implémentées par des membres de la communauté travaillant sur le projet open source Roslyn.These new locations for expression-bodied members represent an important milestone for the C# language: These features were implemented by community members working on the open-source Roslyn project.

Expressions throwThrow expressions

En C#, throw a toujours été une instruction.In C#, throw has always been a statement. Étant donné que throw est une instruction, et non pas une expression, certaines constructions C# ne pouvaient pas l’utiliser.Because throw is a statement, not an expression, there were C# constructs where you could not use it. Il s’agit notamment des expressions conditionnelles, des expressions de fusion null, ainsi que de certaines expressions lambda.These included conditional expressions, null coalescing expressions, and some lambda expressions. L’ajout de membres expression-bodied ajoute des emplacements supplémentaires où les expressions throw seraient utiles.The addition of expression-bodied members adds more locations where throw expressions would be useful. Pour vous permettre d’écrire n’importe laquelle de ces constructions, C# 7 introduit les expressions throw.So that you can write any of these constructs, C# 7 introduces throw expressions.

La syntaxe est la même que celle que vous avez toujours utilisée pour les instructions throw.The syntax is the same as you've always used for throw statements. La seule différence est que vous pouvez maintenant les placer dans de nouveaux emplacements, comme dans une expression conditionnelle :The only difference is that now you can place them in new locations, such as in a conditional expression:

public string Name
{
    get => name;
    set => name = value ?? 
        throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");
}

Cette fonctionnalité permet d’utiliser des expressions throw dans des expressions d’initialisation :This features enables using throw expressions in initialization expressions:

private ConfigResource loadedConfig = LoadConfigResourceOrDefault() ?? 
    throw new InvalidOperationException("Could not load config");

Auparavant, ces initialisations devaient se trouver dans un constructeur, avec les instructions throw dans le corps du constructeur :Previously, those initializations would need to be in a constructor, with the throw statements in the body of the constructor:

public ApplicationOptions()
{
    loadedConfig = LoadConfigResourceOrDefault();
    if (loadedConfig == null)
        throw new InvalidOperationException("Could not load config");

}

Note

Les deux constructions précédentes provoquent la levée d’exceptions pendant la construction d’un objet.Both of the preceding constructs will cause exceptions to be thrown during the construction of an object. Celles-ci sous souvent difficilement récupérables.Those are often difficult to recover from. C’est pourquoi les conceptions qui lèvent des exceptions lors de la construction sont déconseillées.For that reason, designs that throw exceptions during construction are discouraged.

Types de retour async généralisésGeneralized async return types

Le retour d’un objet Task à partir de méthodes async peut introduire des goulots d’étranglement au niveau des performances dans certains chemins.Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task est un type référence. Si vous l’utilisez, vous allouez donc un objet.Task is a reference type, so using it means allocating an object. Dans les cas où une méthode déclarée avec le modificateur async retourne un résultat mis en cache, ou si elle s’exécute de manière synchrone, le coût en termes de temps induit par les allocations supplémentaires peut s’avérer significatif dans les sections de code critiques pour les performances.In cases where a method declared with the async modifier returns a cached result, or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. Il peut devenir très important si ces allocations se produisent dans des boucles serrées.It can become very costly if those allocations occur in tight loops.

La nouvelle fonctionnalité du langage signifie que les méthodes async peuvent retourner d’autres types en plus de Task, de Task<T> et de void.The new language feature means that async methods may return other types in addition to Task, Task<T> and void. Le type retourné doit toujours correspondre au modèle async, ce qui signifie qu’une méthode GetAwaiter doit être accessible.The returned type must still satisfy the async pattern, meaning a GetAwaiter method must be accessible. Pour donner un exemple concret, le type ValueTask a été ajouté au .NET Framework pour utiliser cette nouvelle fonctionnalité du langage :As one concrete example, the ValueTask type has been added to the .NET framework to make use of this new language feature:

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

Note

Vous devez ajouter le package NuGet System.Threading.Tasks.Extensions pour pouvoir utiliser le type ValueTask<TResult>.You need to add the NuGet package System.Threading.Tasks.Extensions in order to use the ValueTask<TResult> type.

Une optimisation simple consisterait à utiliser ValueTask dans des emplacements où Task était utilisé avant.A simple optimization would be to use ValueTask in places where Task would be used before. Toutefois, si vous voulez effectuer manuellement des optimisations supplémentaires, vous pouvez mettre en cache les résultats à partir du travail asynchrone et réutiliser le résultat dans les appels suivants.However, if you want to perform extra optimizations by hand, you can cache results from async work and reuse the result in subsequent calls. Le struct ValueTask a un constructeur avec un paramètre Task pour vous permettre de construire un ValueTask à partir de la valeur de retour de toute méthode async existante :The ValueTask struct has a constructor with a Task parameter so that you can construct a ValueTask from the return value of any existing async method:

public ValueTask<int> CachedFunc()
{
    return (cache) ? new ValueTask<int>(cacheResult) : new ValueTask<int>(LoadCache());
}
private bool cache = false;
private int cacheResult;
private async Task<int> LoadCache()
{
    // simulate async work:
    await Task.Delay(100);
    cacheResult = 100;
    cache = true;
    return cacheResult;
}

Comme avec toutes les recommandations relatives aux performances, vous devez effectuer un test d’évaluation sur les deux versions avant d’apporter des changements à grande échelle à votre code.As with all performance recommendations, you should benchmark both versions before making large scale changes to your code.

Améliorations de la syntaxe littérale numériqueNumeric literal syntax improvements

La mauvaise interprétation de constantes numériques peut rendre plus difficile la compréhension du code quand il est lu pour la première fois.Misreading numeric constants can make it harder to understand code when reading it for the first time. Cela se produit souvent quand ces nombres sont utilisés comme des masques de bits ou un autre élément symbolique plutôt que comme des valeurs numériques.This often occurs when those numbers are used as bit masks or other symbolic rather than numeric values. C# 7 comprend deux nouvelles fonctionnalités permettant de faciliter l’écriture de nombres de la manière la plus lisible pour l’utilisation prévue : les littéraux binaires et les séparateurs de chiffres.C# 7 includes two new features to make it easier to write numbers in the most readable fashion for the intended use: binary literals, and digit separators.

Dans les cas où vous créez des masques de bits chaque fois qu’une représentation binaire d’un nombre rend le code plus lisible, écrivez ce nombre au format binaire :For those times when you are creating bit masks, or whenever a binary representation of a number makes the most readable code, write that number in binary:

public const int One =  0b0001;
public const int Two =  0b0010;
public const int Four = 0b0100;
public const int Eight = 0b1000;

Le 0b au début de la constante indique que le nombre est écrit sous la forme d’un nombre binaire.The 0b at the beginning of the constant indicates that the number is written as a binary number.

Les nombres binaires peuvent être très longs. Il est donc souvent plus facile de voir les modèles de bits en introduisant le _ comme séparateur de chiffres :Binary numbers can get very long, so it's often easier to see the bit patterns by introducing the _ as a digit separator:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

Le séparateur de chiffres peut apparaître n’importe où dans la constante.The digit separator can appear anywhere in the constant. Pour les nombres de base 10, il arrive fréquemment qu’il soit utilisé comme séparateur des milliers :For base 10 numbers, it would be common to use it as a thousands separator:

public const long BillionsAndBillions = 100_000_000_000;

Il est possible d’utiliser le séparateur de chiffres également avec les types decimal, float et double :The digit separator can be used with decimal, float and double types as well:

public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

Globalement, vous pouvez déclarer des constantes numériques avec beaucoup plus de lisibilité.Taken together, you can declare numeric constants with much more readability.