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

C#8.0 ajoute les fonctionnalités suivantes et les améliorations apportées au langage C# :C# 8.0 adds the following features and enhancements to the C# language:

C#8,0 est pris en charge sur .net Core 3. x et .NET standard 2,1.C# 8.0 is supported on .NET Core 3.x and .NET Standard 2.1. Pour plus d’informations, C# consultez contrôle de version de langage.For more information, see C# language versioning.

La suite de cet article décrit brièvement ces fonctionnalités.The remainder of this article briefly describes these features. Lorsque des articles détaillés sont disponibles, des liens vers ces tutoriels et vues d’ensemble sont indiqués.Where in-depth articles are available, links to those tutorials and overviews are provided. Vous pouvez explorer ces fonctionnalités dans votre environnement à l’aide de l’outil global dotnet try :You can explore these features in your environment using the dotnet try global tool:

  1. Installez l’outil global dotnet-try.Install the dotnet-try global tool.
  2. Clonez le référentiel dotnet/try-samples.Clone the dotnet/try-samples repository.
  3. Définissez le répertoire actuel sur le sous-répertoire csharp8 pour le référentiel try-samples.Set the current directory to the csharp8 subdirectory for the try-samples repository.
  4. Exécutez dotnet try.Run dotnet try.

Membres ReadOnlyReadonly members

Vous pouvez appliquer le modificateur readonly aux membres d’un struct.You can apply the readonly modifier to members of a struct. Elle indique que le membre ne modifie pas l’État.It indicates that the member doesn't modify state. C’est plus précis que d’appliquer le modificateur readonly à une déclaration struct.It's more granular than applying the readonly modifier to a struct declaration. Examinons le struct mutable suivant :Consider the following mutable struct:

public struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Distance => Math.Sqrt(X * X + Y * Y);

    public override string ToString() =>
        $"({X}, {Y}) is {Distance} from the origin";
}

Comme la plupart des structs, la méthode ToString() ne modifie pas l’État.Like most structs, the ToString() method doesn't modify state. Vous pouvez indiquer cela en ajoutant le modificateur readonly à la déclaration de ToString() :You could indicate that by adding the readonly modifier to the declaration of ToString():

public readonly override string ToString() =>
    $"({X}, {Y}) is {Distance} from the origin";

La modification précédente génère un avertissement du compilateur, car ToString accède à la propriété Distance, qui n’est pas marquée readonly:The preceding change generates a compiler warning, because ToString accesses the Distance property, which isn't marked readonly:

warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'

Le compilateur vous avertit lorsqu’il a besoin de créer une copie défensive.The compiler warns you when it needs to create a defensive copy. La propriété Distance ne change pas d’État. vous pouvez donc résoudre cet avertissement en ajoutant le modificateur readonly à la déclaration :The Distance property doesn't change state, so you can fix this warning by adding the readonly modifier to the declaration:

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

Notez que le modificateur readonly est nécessaire sur une propriété en lecture seule.Notice that the readonly modifier is necessary on a read-only property. Le compilateur ne suppose pas que les accesseurs get ne modifient pas l’État ; vous devez déclarer readonly explicitement.The compiler doesn't assume get accessors don't modify state; you must declare readonly explicitly. Les propriétés implémentées automatiquement sont une exception. le compilateur traite tous les getters implémentés automatiquement comme ReadOnly. il n’est donc pas nécessaire d’ajouter le modificateur readonly aux propriétés X et Y.Auto-implemented properties are an exception; the compiler will treat all auto-implemented getters as readonly, so here there's no need to add the readonly modifier to the X and Y properties.

Le compilateur applique la règle qui readonly membres ne modifient pas l’État.The compiler does enforce the rule that readonly members don't modify state. La méthode suivante n’est pas compilée, sauf si vous supprimez le modificateur readonly :The following method won't compile unless you remove the readonly modifier:

public readonly void Translate(int xOffset, int yOffset)
{
    X += xOffset;
    Y += yOffset;
}

Cette fonctionnalité vous permet de spécifier votre intention de conception, afin que le compilateur puisse l’appliquer et procéder à des optimisations basées sur cette intention.This feature lets you specify your design intent so the compiler can enforce it, and make optimizations based on that intent. Vous pouvez en savoir plus sur les membres en lecture seule dans l’article de référence sur les langages sur readonly.You can learn more about readonly members in the language reference article on readonly.

Méthodes d’interface par défautDefault interface methods

Vous pouvez désormais ajouter des membres aux interfaces et fournir une implémentation pour ces membres.You can now add members to interfaces and provide an implementation for those members. Cette fonctionnalité de langage permet aux auteurs d’API d’ajouter des méthodes à une interface dans les versions ultérieures sans pour autant nuire à la compatibilité des binaires ou des sources avec les implémentations existantes de cette interface.This language feature enables API authors to add methods to an interface in later versions without breaking source or binary compatibility with existing implementations of that interface. Les implémentations existantes héritent de l’implémentation par défaut.Existing implementations inherit the default implementation. Cette fonctionnalité permet également à C# d’interagir avec les API ciblant Android ou Swift, qui prennent en charge des fonctionnalités similaires.This feature also enables C# to interoperate with APIs that target Android or Swift, which support similar features. Les méthodes d’interface par défaut permettent également des scénarios semblables à une fonctionnalité de langage « traits ».Default interface methods also enable scenarios similar to a "traits" language feature.

Les méthodes d’interface par défaut affectent de nombreux scénarios et éléments de langage.Default interface methods affects many scenarios and language elements. Ce premier tutoriel couvre la mise à jour d’une interface à l’aide d’implémentations par défaut.Our first tutorial covers updating an interface with default implementations. D’autres tutoriels et mises à jour de référence seront disponibles avant le lancement général.Other tutorials and reference updates are coming in time for general release.

Ajout de modèles à différents endroitsMore patterns in more places

Les critères spéciaux offrent des outils permettant de produire des fonctionnalités dépendantes de la forme sur des types de données liés mais différents.Pattern matching gives tools to provide shape-dependent functionality across related but different kinds of data. C# 7.0 a introduit la syntaxe des modèles de type et des modèles de constantes avec l’expression is et l’instruction switch.C# 7.0 introduced syntax for type patterns and constant patterns by using the is expression and the switch statement. Ces fonctionnalités représentaient les premières étapes provisoires de prise en charge de paradigmes de programmation distinguant données et fonctionnalités.These features represented the first tentative steps toward supporting programming paradigms where data and functionality live apart. Face à la transition du secteur vers de nouveaux microservices et autres architectures cloud, d’autres outils sont nécessaires pour le langage.As the industry moves toward more microservices and other cloud-based architectures, other language tools are needed.

C# 8.0 développe ce vocabulaire en offrant la possibilité d’utiliser d’autres expressions de modèle à davantage d’endroits dans le code.C# 8.0 expands this vocabulary so you can use more pattern expressions in more places in your code. Étudiez ces fonctionnalités si vos données et vos fonctionnalités sont séparées.Consider these features when your data and functionality are separate. Les critères spéciaux peuvent être intéressants si vos algorithmes dépendent d’un fait autre que le type de runtime d’un objet.Consider pattern matching when your algorithms depend on a fact other than the runtime type of an object. Ces techniques représentent un autre moyen d’exprimer des conceptions.These techniques provide another way to express designs.

En plus des nouveaux modèles en de nouveaux endroits, C# 8.0 ajoute des modèles récursifs.In addition to new patterns in new places, C# 8.0 adds recursive patterns. Le résultat d’une expression de modèle est une expression.The result of any pattern expression is an expression. Un modèle récursif constitue simplement une expression de modèle appliquée à la sortie d’une autre expression de modèle.A recursive pattern is simply a pattern expression applied to the output of another pattern expression.

Expressions switchSwitch expressions

Souvent, une instruction switch produit une valeur dans chacun de ses blocs case.Often, a switch statement produces a value in each of its case blocks. Les expressions switch permettent d’utiliser une syntaxe d’expression plus concise,Switch expressions enable you to use more concise expression syntax. comprenant moins de mots clés case et break répétitifs et moins d’accolades.There are fewer repetitive case and break keywords, and fewer curly braces. Prenons par exemple l’enum suivant, qui liste les couleurs de l’arc-en-ciel :As an example, consider the following enum that lists the colors of the rainbow:

public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}

Si votre application a défini un type RGBColor construit à partir des composants R, G et B, vous pouvez convertir une valeur Rainbow en valeurs RVB avec la méthode suivante, qui contient une expression switch :If your application defined an RGBColor type that is constructed from the R, G and B components, you could convert a Rainbow value to its RGB values using the following method containing a switch expression:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

Plusieurs améliorations ont ici été apportées à la syntaxe :There are several syntax improvements here:

  • La variable se situe avant le mot clé switch.The variable comes before the switch keyword. Ce nouvel ordre permet de distinguer visuellement l’expression switch de l’instruction switch.The different order makes it visually easy to distinguish the switch expression from the switch statement.
  • Les éléments case et : sont remplacés par =>,The case and : elements are replaced with =>. plus concis et plus intuitif.It's more concise and intuitive.
  • Le cas default est remplacé par un discard _.The default case is replaced with a _ discard.
  • Les corps sont des expressions et non des instructions.The bodies are expressions, not statements.

Comparez cela avec le code équivalent utilisant l’instruction switch classique :Contrast that with the equivalent code using the classic switch statement:

public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Orange:
            return new RGBColor(0xFF, 0x7F, 0x00);
        case Rainbow.Yellow:
            return new RGBColor(0xFF, 0xFF, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        case Rainbow.Indigo:
            return new RGBColor(0x4B, 0x00, 0x82);
        case Rainbow.Violet:
            return new RGBColor(0x94, 0x00, 0xD3);
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
    };
}

Modèles de propriétésProperty patterns

Le modèle de propriété permet de faire correspondre les propriétés de l’objet examiné.The property pattern enables you to match on properties of the object examined. Prenons un site d’e-commerce qui doit calculer les taxes sur les ventes en fonction de l’adresse de l’acheteur.Consider an eCommerce site that must compute sales tax based on the buyer's address. Ce calcul n’est pas une responsabilité fondamentale d’une classe Address.That computation isn't a core responsibility of an Address class. Il changera au fil du temps, probablement plus souvent que n’évoluera le format de l’adresse.It will change over time, likely more often than address format changes. Le montant des taxes sur les ventes varie selon la propriété State de l’adresse.The amount of sales tax depends on the State property of the address. La méthode suivante utilise le modèle de propriété pour calculer les taxes sur les ventes à partir de l’adresse et du prix :The following method uses the property pattern to compute the sales tax from the address and the price:

public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
    location switch
    {
        { State: "WA" } => salePrice * 0.06M,
        { State: "MN" } => salePrice * 0.75M,
        { State: "MI" } => salePrice * 0.05M,
        // other cases removed for brevity...
        _ => 0M
    };

Les critères spéciaux offrent une syntaxe concise pour exprimer cet algorithme.Pattern matching creates a concise syntax for expressing this algorithm.

Modèles de tuplesTuple patterns

Certains algorithmes dépendent de plusieurs entrées.Some algorithms depend on multiple inputs. Les modèles de tuples permettent de basculer des unes aux autres en fonction de plusieurs valeurs exprimées sous forme de tuple.Tuple patterns allow you to switch based on multiple values expressed as a tuple. Le code suivant montre une expression switch pour le jeu Pierre-papier-ciseaux :The following code shows a switch expression for the game rock, paper, scissors:

public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };

Les messages indiquent le vainqueur.The messages indicate the winner. Le cas discard représente les trois combinaisons pour les égalités, ou d’autres entrées de texte.The discard case represents the three combinations for ties, or other text inputs.

Modèles positionnelsPositional patterns

Certains types comportent une méthode Deconstruct qui décompose ses propriétés en variables discrètes.Some types include a Deconstruct method that deconstructs its properties into discrete variables. Lorsqu’une méthode Deconstruct est accessible, il est possible d’utiliser des modèles positionnels pour inspecter les propriétés de l’objet et de se servir de ces dernières pour un modèle.When a Deconstruct method is accessible, you can use positional patterns to inspect properties of the object and use those properties for a pattern. Considérez la classe Point suivante, dont la méthode Deconstruct sert à créer des variables discrètes pour X et Y :Consider the following Point class that includes a Deconstruct method to create discrete variables for X and Y:

public class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);

    public void Deconstruct(out int x, out int y) =>
        (x, y) = (X, Y);
}

De plus, envisagez l’énumération suivante qui représente diverses positions d’un quadrant :Additionally, consider the following enum that represents various positions of a quadrant:

public enum Quadrant
{
    Unknown,
    Origin,
    One,
    Two,
    Three,
    Four,
    OnBorder
}

La méthode suivante utilise le modèle positionnel pour extraire les valeurs de x et de y.The following method uses the positional pattern to extract the values of x and y. Ensuite, elle utilise une clause when pour déterminer le Quadrant du point :Then, it uses a when clause to determine the Quadrant of the point:

static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

Le modèle discard dans le switch précédent indique une correspondance lorsque soit x, soit y est égal à 0, mais pas les deux.The discard pattern in the preceding switch matches when either x or y is 0, but not both. Une expression switch doit produire une valeur ou,A switch expression must either produce a value or throw an exception. si aucun des cas ne correspond, lever une exception.If none of the cases match, the switch expression throws an exception. Le compilateur génère un avertissement pour vous si vous ne traitez pas tous les cas possibles dans votre expression de commutateur.The compiler generates a warning for you if you don't cover all possible cases in your switch expression.

Vous pouvez explorer des techniques de critères spéciaux dans ce tutoriel avancé sur les critères spéciaux.You can explore pattern matching techniques in this advanced tutorial on pattern matching.

Déclarations usingUsing declarations

Une déclaration using est une déclaration de variable précédée par le mot clé using.A using declaration is a variable declaration preceded by the using keyword. Elle indique au compilateur que la variable déclarée doit être supprimée à la fin de la portée englobante.It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope. Prenons par exemple le code suivant, qui écrit un fichier texte :For example, consider the following code that writes a text file:

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    // Notice how we declare skippedLines after the using statement.
    int skippedLines = 0;
    foreach (string line in lines)
    {
        if (!line.Contains("Second"))
        {
            file.WriteLine(line);
        }
        else
        {
            skippedLines++;
        }
    }
    // Notice how skippedLines is in scope here.
    return skippedLines;
    // file is disposed here
}

Dans l’exemple précédent, le fichier est supprimé une fois l’accolade fermante de la méthode atteinte.In the preceding example, the file is disposed when the closing brace for the method is reached. C’est la fin de la portée dans laquelle file est déclaré.That's the end of the scope in which file is declared. Le code précédent équivaut au suivant, qui utilise l’instruction using classique :The preceding code is equivalent to the following code that uses the classic using statement:

static int WriteLinesToFile(IEnumerable<string> lines)
{
    // We must declare the variable outside of the using block
    // so that it is in scope to be returned.
    int skippedLines = 0;
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        foreach (string line in lines)
        {
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
            else
            {
                skippedLines++;
            }
        }
    } // file is disposed here
    return skippedLines;
}

Dans l’exemple précédent, le fichier est supprimé une fois l’accolade fermante associée à l’instruction using atteinte.In the preceding example, the file is disposed when the closing brace associated with the using statement is reached.

Dans les deux cas, le compilateur génère l’appel à Dispose().In both cases, the compiler generates the call to Dispose(). Le compilateur génère une erreur si l’expression dans l’instruction using n’est pas supprimable.The compiler generates an error if the expression in the using statement isn't disposable.

Fonctions locales statiquesStatic local functions

Il est maintenant possible d’ajouter le modificateur static à des fonctions locales pour éviter qu’elles ne capturent (fassent référence à) des variables au sein de la portée englobante.You can now add the static modifier to local functions to ensure that local function doesn't capture (reference) any variables from the enclosing scope. Cela génère CS8421, « A static local function can’t contain a reference to < variable> ».Doing so generates CS8421, "A static local function can't contain a reference to <variable>."

Prenons le code suivant.Consider the following code. La fonction locale LocalFunction accède à la variable y, déclarée dans la portée englobante (la méthode M).The local function LocalFunction accesses the variable y, declared in the enclosing scope (the method M). Par conséquent, LocalFunction ne peut pas être déclarée avec le modificateur static :Therefore, LocalFunction can't be declared with the static modifier:

int M()
{
    int y;
    LocalFunction();
    return y;

    void LocalFunction() => y = 0;
}

Le code suivant contient une fonction locale statique.The following code contains a static local function. Elle peut être statique, car elle n’accède à aucune variable dans la portée englobante :It can be static because it doesn't access any variables in the enclosing scope:

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

Structs ref jetablesDisposable ref structs

Un struct déclaré avec le modificateur ref peut ne pas implémenter d’interfaces et ne peut donc pas implémenter IDisposable.A struct declared with the ref modifier may not implement any interfaces and so can't implement IDisposable. Par conséquent, pour qu’un ref struct soit supprimable, il doit avoir une méthode void Dispose() accessible.Therefore, to enable a ref struct to be disposed, it must have an accessible void Dispose() method. Cette fonctionnalité s’applique également aux déclarations de readonly ref struct.This feature also applies to readonly ref struct declarations.

Types références NullablesNullable reference types

Dans un contexte d’annotation Nullable, toute variable d’un type référence est considérée comme un type référence n'acceptant pas la valeur Null.Inside a nullable annotation context, any variable of a reference type is considered to be a nonnullable reference type. Si vous souhaitez indiquer qu’une variable peut être Null, ajoutez ? au nom de type pour la déclarer comme type référence Nullable.If you want to indicate that a variable may be null, you must append the type name with the ? to declare the variable as a nullable reference type.

Avec les types références n'acceptant pas la valeur Null, le compilateur utilise l’analyse de flux pour que les variables locales soient initialisées à une valeur non Null une fois déclarées.For nonnullable reference types, the compiler uses flow analysis to ensure that local variables are initialized to a non-null value when declared. Les champs doivent être initialisés à la construction.Fields must be initialized during construction. Le compilateur génère un avertissement si la variable n’est pas définie par un appel à l’un des constructeurs disponibles ou par un initialiseur.The compiler generates a warning if the variable isn't set by a call to any of the available constructors or by an initializer. Par ailleurs, les types références n’acceptant pas la valeur Null ne peuvent pas avoir de valeur pouvant être Null.Furthermore, nonnullable reference types can't be assigned a value that could be null.

Les types références Nullables font l’objet d’aucun contrôle visant à vérifier qu’ils ne sont pas affectés ou initialisées sur Null.Nullable reference types aren't checked to ensure they aren't assigned or initialized to null. Toutefois, le compilateur utilise l’analyse de flux pour comparer à Null toutes les variables d’un type référence Nullable avant d’y accéder ou de lui affecter un type référence n’acceptant pas la valeur Null.However, the compiler uses flow analysis to ensure that any variable of a nullable reference type is checked against null before it's accessed or assigned to a nonnullable reference type.

Plus d’informations sur la fonctionnalité, voir la vue d’ensemble des types références Nullables.You can learn more about the feature in the overview of nullable reference types. Essayez par vous-même dans une nouvelle application avec ce tutoriel des types références Nullables.Try it yourself in a new application in this nullable reference types tutorial. Pour plus d’informations sur le processus de migration d’un codebase dans l’objectif d’utiliser des types références Nullables, voir le tutoriel Migrer une application pour utiliser des types références Nullables.Learn about the steps to migrate an existing codebase to make use of nullable reference types in the migrating an application to use nullable reference types tutorial.

Flux asynchronesAsynchronous streams

À partir de C# 8.0, il est possible de créer et de consommer des flux de façon asynchrone.Starting with C# 8.0, you can create and consume streams asynchronously. Une méthode qui retourne un flux asynchrone comporte trois propriétés :A method that returns an asynchronous stream has three properties:

  1. Elle est déclarée avec le modificateur async.It's declared with the async modifier.
  2. Elle retourne un IAsyncEnumerable<T>.It returns an IAsyncEnumerable<T>.
  3. La méthode contient des instructions yield return pour retourner des éléments consécutifs dans le flux asynchrone.The method contains yield return statements to return successive elements in the asynchronous stream.

Pour pouvoir consommer un flux asynchrone, il faut ajouter le mot clé await avant le mot clé foreach au moment d’énumérer les éléments du flux.Consuming an asynchronous stream requires you to add the await keyword before the foreach keyword when you enumerate the elements of the stream. Le mot clé await exige que la méthode qui énumère le flux asynchrone soit déclarée avec le modificateur async et retourne un type autorisé pour une méthode async,Adding the await keyword requires the method that enumerates the asynchronous stream to be declared with the async modifier and to return a type allowed for an async method. soit en général Task ou Task<TResult>.Typically that means returning a Task or Task<TResult>. Il peut également s’agir de ValueTask ou ValueTask<TResult>.It can also be a ValueTask or ValueTask<TResult>. Une méthode peut consommer et produire un flux asynchrone ; elle retournerait alors un IAsyncEnumerable<T>.A method can both consume and produce an asynchronous stream, which means it would return an IAsyncEnumerable<T>. Le code suivant génère une séquence de 0 à 19, en attendant 100 ms entre chaque nombre :The following code generates a sequence from 0 to 19, waiting 100 ms between generating each number:

public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

Pour énumérer la séquence, on utilise l’instruction await foreach :You would enumerate the sequence using the await foreach statement:

await foreach (var number in GenerateSequence())
{
    Console.WriteLine(number);
}

Vous pouvez essayer par vous-même les flux asynchrones dans notre tutoriel Créer et consommer des flux asynchrones.You can try asynchronous streams yourself in our tutorial on creating and consuming async streams.

Index et plagesIndices and ranges

Les index et les plages fournissent une syntaxe concise pour accéder à des éléments ou des plages uniques dans une séquence.Indices and ranges provide a succinct syntax for accessing single elements or ranges in a sequence.

Cette prise en charge de langage s’appuie sur deux nouveaux types et deux nouveaux opérateurs :This language support relies on two new types, and two new operators:

  • System.Index représente un index au sein d’une séquence.System.Index represents an index into a sequence.
  • L’index de l’opérateur end ^, qui spécifie qu’un index est relatif à la fin de la séquence.The index from end operator ^, which specifies that an index is relative to the end of the sequence.
  • System.Range représente une sous-plage d’une séquence.System.Range represents a sub range of a sequence.
  • L’opérateur de plage .., qui spécifie le début et la fin d’une plage comme opérandes.The range operator .., which specifies the start and end of a range as its operands.

Commençons par les règles concernant les index.Let's start with the rules for indexes. Prenons pour exemple un tableau sequence.Consider an array sequence. L’index 0 est identique à l’index sequence[0].The 0 index is the same as sequence[0]. L’index ^0 est identique à l’index sequence[sequence.Length].The ^0 index is the same as sequence[sequence.Length]. Notez que sequence[^0] lève une exception, tout comme sequence[sequence.Length].Note that sequence[^0] does throw an exception, just as sequence[sequence.Length] does. Pour n’importe quel nombre n, l’index ^n est le même que l’index sequence.Length - n.For any number n, the index ^n is the same as sequence.Length - n.

Une plage spécifie son début et sa fin.A range specifies the start and end of a range. Le début de la plage est compris, mais la fin de la plage est exclusive, ce qui signifie que le début est inclus dans la plage, mais que la fin n’est pas incluse dans la plage.The start of the range is inclusive, but the end of the range is exclusive, meaning the start is included in the range but the end isn't included in the range. La plage [0..^0] représente la plage dans son intégralité, tout comme [0..sequence.Length] représente la plage entière.The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

Prenons quelques exemples.Let's look at a few examples. Examinez le tableau suivant, annoté avec son index à partir du début et de la fin :Consider the following array, annotated with its index from the start and from the end:

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

Vous pouvez récupérer le dernier mot avec l’index ^1 :You can retrieve the last word with the ^1 index:

Console.WriteLine($"The last word is {words[^1]}");
// writes "dog"

Le code suivant crée une sous-plage qui comporte les mots « quick », « brown » et « fox »The following code creates a subrange with the words "quick", "brown", and "fox". et va de words[1] à words[3].It includes words[1] through words[3]. L’élément words[4] n’est pas dans la plage.The element words[4] isn't in the range.

var quickBrownFox = words[1..4];

Le code suivant crée une sous-plage qui comporte « lazy » et « dog »The following code creates a subrange with "lazy" and "dog". et comprend words[^2] et words[^1].It includes words[^2] and words[^1]. L’index de fin words[^0] n’est pas inclus :The end index words[^0] isn't included:

var lazyDog = words[^2..^0];

Les exemples suivants créent des plages ouvertes au début, à la fin ou les deux :The following examples create ranges that are open ended for the start, end, or both:

var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"

Vous pouvez également déclarer des plages comme variables :You can also declare ranges as variables:

Range phrase = 1..4;

La plage peut ensuite être utilisée à l’intérieur des caractères [ et ] :The range can then be used inside the [ and ] characters:

var text = words[phrase];

Non seulement les tableaux prennent en charge les index et les plages.Not only arrays support indices and ranges. Vous pouvez également utiliser des index et des plages avec chaîne, Span<T>ou ReadOnlySpan<T>.You also can use indices and ranges with string, Span<T>, or ReadOnlySpan<T>. Pour plus d’informations, consultez prise en charge des types d’index et de plages.For more information, see Type support for indices and ranges.

Pour explorer davantage les index et les plages, consultez le tutoriel sur les index et les plages.You can explore more about indices and ranges in the tutorial on indices and ranges.

Assignation de fusion NullNull-coalescing assignment

C#8,0 introduit l’opérateur d’assignation de fusion Null ??=.C# 8.0 introduces the null-coalescing assignment operator ??=. Vous pouvez utiliser l’opérateur ??= pour assigner la valeur de son opérande droit à son opérande gauche uniquement si l’opérande de gauche prend la valeur null.You can use the ??= operator to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null.

List<int> numbers = null;
int? i = null;

numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);

Console.WriteLine(string.Join(" ", numbers));  // output: 17 17
Console.WriteLine(i);  // output: 17

Pour plus d’informations, consultez les = l’article Operators .For more information, see the ?? and ??= operators article.

Types construits non managésUnmanaged constructed types

Dans C# 7,3 et les versions antérieures, un type construit (un type qui comprend au moins un argument de type) ne peut pas être un type non managé.In C# 7.3 and earlier, a constructed type (a type that includes at least one type argument) can't be an unmanaged type. À partir de C# 8.0, un type valeur construit est non managé s’il contient uniquement des champs de types non managés.Starting with C# 8.0, a constructed value type is unmanaged if it contains fields of unmanaged types only.

Par exemple, étant donné la définition suivante du type de Coords<T> générique :For example, given the following definition of the generic Coords<T> type:

public struct Coords<T>
{
    public T X;
    public T Y;
}

le type de Coords<int> est un type non managé dans C# 8,0 et versions ultérieures.the Coords<int> type is an unmanaged type in C# 8.0 and later. Comme pour tout type non managé, vous pouvez créer un pointeur vers une variable de ce type ou allouer un bloc de mémoire sur la pile pour les instances de ce type :Like for any unmanaged type, you can create a pointer to a variable of this type or allocate a block of memory on the stack for instances of this type:

Span<Coords<int>> coordinates = stackalloc[]
{
    new Coords<int> { X = 0, Y = 0 },
    new Coords<int> { X = 0, Y = 3 },
    new Coords<int> { X = 4, Y = 0 }
};

Pour plus d’informations, consultez types non managés.For more information, see Unmanaged types.

Stackalloc dans les expressions imbriquéesStackalloc in nested expressions

À C# partir de 8,0, si le résultat d’une expression stackalloc est du type System.Span<T> ou System.ReadOnlySpan<T>, vous pouvez utiliser l’expression stackalloc dans d’autres expressions :Starting with C# 8.0, if the result of a stackalloc expression is of the System.Span<T> or System.ReadOnlySpan<T> type, you can use the stackalloc expression in other expressions:

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };
var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6 ,8 });
Console.WriteLine(ind);  // output: 1

Amélioration des chaînes textuelles interpoléesEnhancement of interpolated verbatim strings

L’ordre des jetons de $ et de @ dans les chaînes textuelles interpolées peut être n’importe quel : $@"..." et @$"..." sont des chaînes textuelles interpolées valides.Order of the $ and @ tokens in interpolated verbatim strings can be any: both $@"..." and @$"..." are valid interpolated verbatim strings. Dans les C# versions antérieures, le jeton $ doit apparaître avant le jeton @.In earlier C# versions, the $ token must appear before the @ token.